diff --git a/app/app.go b/app/app.go deleted file mode 100644 index 501717a9..00000000 --- a/app/app.go +++ /dev/null @@ -1,254 +0,0 @@ -// Copyright 2016 All in Bits, inc -// Modifications copyright 2019 Kava Labs - -package app - -import ( - "io" - "os" - - abci "github.com/tendermint/tendermint/abci/types" - cmn "github.com/tendermint/tendermint/libs/common" - dbm "github.com/tendermint/tendermint/libs/db" - "github.com/tendermint/tendermint/libs/log" - - 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 -) - -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") -} - -// Extended ABCI application -type App struct { - *bam.BaseApp - cdc *codec.Codec - - invCheckPeriod uint - - // keys to access the substores - keyMain *sdk.KVStoreKey - keyAccount *sdk.KVStoreKey - keyStaking *sdk.KVStoreKey - tkeyStaking *sdk.TransientStoreKey - keySlashing *sdk.KVStoreKey - keyMint *sdk.KVStoreKey - keyDistr *sdk.KVStoreKey - tkeyDistr *sdk.TransientStoreKey - keyGov *sdk.KVStoreKey - keyFeeCollection *sdk.KVStoreKey - keyParams *sdk.KVStoreKey - tkeyParams *sdk.TransientStoreKey - - // keepers - accountKeeper auth.AccountKeeper - feeCollectionKeeper auth.FeeCollectionKeeper - bankKeeper bank.Keeper - stakingKeeper staking.Keeper - slashingKeeper slashing.Keeper - mintKeeper mint.Keeper - distrKeeper distr.Keeper - 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 { - - cdc := MakeCodec() - - bApp := bam.NewBaseApp(appName, logger, db, auth.DefaultTxDecoder(cdc), baseAppOptions...) - bApp.SetCommitMultiStoreTracer(traceStore) - bApp.SetAppVersion(version.Version) - - var app = &App{ - BaseApp: bApp, - cdc: cdc, - invCheckPeriod: invCheckPeriod, - keyMain: sdk.NewKVStoreKey(bam.MainStoreKey), - keyAccount: sdk.NewKVStoreKey(auth.StoreKey), - keyStaking: sdk.NewKVStoreKey(staking.StoreKey), - tkeyStaking: sdk.NewTransientStoreKey(staking.TStoreKey), - keyMint: sdk.NewKVStoreKey(mint.StoreKey), - keyDistr: sdk.NewKVStoreKey(distr.StoreKey), - tkeyDistr: sdk.NewTransientStoreKey(distr.TStoreKey), - keySlashing: sdk.NewKVStoreKey(slashing.StoreKey), - keyGov: sdk.NewKVStoreKey(gov.StoreKey), - keyFeeCollection: sdk.NewKVStoreKey(auth.FeeStoreKey), - keyParams: sdk.NewKVStoreKey(params.StoreKey), - 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) - - // 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), - ) - - // 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) - - app.mm.SetOrderEndBlockers(gov.ModuleName, staking.ModuleName) - - // 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) - - app.mm.RegisterInvariants(&app.crisisKeeper) - app.mm.RegisterRoutes(app.Router(), app.QueryRouter()) - - // 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) - - // initialize BaseApp - app.SetInitChainer(app.InitChainer) - app.SetBeginBlocker(app.BeginBlocker) - app.SetAnteHandler(auth.NewAnteHandler(app.accountKeeper, app.feeCollectionKeeper, auth.DefaultSigVerificationGasConsumer)) - app.SetEndBlocker(app.EndBlocker) - - if loadLatest { - err := app.LoadLatestVersion(app.keyMain) - if err != nil { - 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) -} - -// application updates every end block -func (app *App) EndBlocker(ctx sdk.Context, req abci.RequestEndBlock) abci.ResponseEndBlock { - return app.mm.EndBlock(ctx, req) -} - -// application update at chain initialization -func (app *App) InitChainer(ctx sdk.Context, req abci.RequestInitChain) abci.ResponseInitChain { - var genesisState GenesisState - app.cdc.MustUnmarshalJSON(req.AppStateBytes, &genesisState) - return app.mm.InitGenesis(ctx, genesisState) -} - -// load a particular height -func (app *App) LoadHeight(height int64) error { - return app.LoadVersion(height, app.keyMain) -} diff --git a/app/genesis.go b/app/genesis.go deleted file mode 100644 index 228bae6d..00000000 --- a/app/genesis.go +++ /dev/null @@ -1,22 +0,0 @@ -// Copyright 2016 All in Bits, inc -// Modifications copyright 2019 Kava Labs - -package app - -import ( - "encoding/json" -) - -// 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 - -// NewDefaultGenesisState generates the default state for gaia. -func NewDefaultGenesisState() GenesisState { - return ModuleBasics.DefaultGenesis() -} diff --git a/gaia/app/app.go b/gaia/app/app.go new file mode 100644 index 00000000..d257627b --- /dev/null +++ b/gaia/app/app.go @@ -0,0 +1,408 @@ +package app + +import ( + "fmt" + "io" + "os" + "sort" + + abci "github.com/tendermint/tendermint/abci/types" + cmn "github.com/tendermint/tendermint/libs/common" + dbm "github.com/tendermint/tendermint/libs/db" + "github.com/tendermint/tendermint/libs/log" + + 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/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/params" + "github.com/cosmos/cosmos-sdk/x/slashing" + "github.com/cosmos/cosmos-sdk/x/staking" +) + +const ( + appName = "GaiaApp" + // DefaultKeyPass contains the default key password for genesis transactions + DefaultKeyPass = "12345678" +) + +// default home directories for expected binaries +var ( + DefaultCLIHome = os.ExpandEnv("$HOME/.gaiacli") + DefaultNodeHome = os.ExpandEnv("$HOME/.gaiad") +) + +// Extended ABCI application +type GaiaApp struct { + *bam.BaseApp + cdc *codec.Codec + + invCheckPeriod uint + + // keys to access the substores + keyMain *sdk.KVStoreKey + keyAccount *sdk.KVStoreKey + keyStaking *sdk.KVStoreKey + tkeyStaking *sdk.TransientStoreKey + keySlashing *sdk.KVStoreKey + keyMint *sdk.KVStoreKey + keyDistr *sdk.KVStoreKey + tkeyDistr *sdk.TransientStoreKey + keyGov *sdk.KVStoreKey + keyFeeCollection *sdk.KVStoreKey + keyParams *sdk.KVStoreKey + tkeyParams *sdk.TransientStoreKey + + // Manage getting and setting accounts + accountKeeper auth.AccountKeeper + feeCollectionKeeper auth.FeeCollectionKeeper + bankKeeper bank.Keeper + stakingKeeper staking.Keeper + slashingKeeper slashing.Keeper + mintKeeper mint.Keeper + distrKeeper distr.Keeper + govKeeper gov.Keeper + crisisKeeper crisis.Keeper + paramsKeeper params.Keeper +} + +// NewGaiaApp returns a reference to an initialized GaiaApp. +func NewGaiaApp(logger log.Logger, db dbm.DB, traceStore io.Writer, loadLatest bool, + invCheckPeriod uint, + baseAppOptions ...func(*bam.BaseApp)) *GaiaApp { + + cdc := MakeCodec() + + bApp := bam.NewBaseApp(appName, logger, db, auth.DefaultTxDecoder(cdc), baseAppOptions...) + bApp.SetCommitMultiStoreTracer(traceStore) + + var app = &GaiaApp{ + BaseApp: bApp, + cdc: cdc, + invCheckPeriod: invCheckPeriod, + keyMain: sdk.NewKVStoreKey(bam.MainStoreKey), + keyAccount: sdk.NewKVStoreKey(auth.StoreKey), + keyStaking: sdk.NewKVStoreKey(staking.StoreKey), + tkeyStaking: sdk.NewTransientStoreKey(staking.TStoreKey), + keyMint: sdk.NewKVStoreKey(mint.StoreKey), + keyDistr: sdk.NewKVStoreKey(distr.StoreKey), + tkeyDistr: sdk.NewTransientStoreKey(distr.TStoreKey), + keySlashing: sdk.NewKVStoreKey(slashing.StoreKey), + keyGov: sdk.NewKVStoreKey(gov.StoreKey), + keyFeeCollection: sdk.NewKVStoreKey(auth.FeeStoreKey), + keyParams: sdk.NewKVStoreKey(params.StoreKey), + tkeyParams: sdk.NewTransientStoreKey(params.TStoreKey), + } + + app.paramsKeeper = params.NewKeeper(app.cdc, app.keyParams, app.tkeyParams) + + // define the accountKeeper + app.accountKeeper = auth.NewAccountKeeper( + app.cdc, + app.keyAccount, + app.paramsKeeper.Subspace(auth.DefaultParamspace), + auth.ProtoBaseAccount, + ) + + // 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, + ) + + // 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()), + ) + + // 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) + + // 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)) + + 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.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)) + app.SetEndBlocker(app.EndBlocker) + + if loadLatest { + err := app.LoadLatestVersion(app.keyMain) + if err != nil { + cmn.Exit(err.Error()) + } + } + + return app +} + +// 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 *GaiaApp) 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 updates every end block +// nolint: unparam +func (app *GaiaApp) 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 *GaiaApp) 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 := GaiaValidateGenesisState(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 gaia initialization +func (app *GaiaApp) initChainer(ctx sdk.Context, req abci.RequestInitChain) abci.ResponseInitChain { + stateJSON := req.AppStateBytes + // TODO is this now the whole genesis file? + + var genesisState 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 *GaiaApp) 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/gaia/app/app_test.go b/gaia/app/app_test.go new file mode 100644 index 00000000..95fa0211 --- /dev/null +++ b/gaia/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(gapp *GaiaApp, 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(gapp.cdc, genesisState) + if err != nil { + return err + } + + // Initialize the chain + vals := []abci.ValidatorUpdate{} + gapp.InitChain(abci.RequestInitChain{Validators: vals, AppStateBytes: stateBytes}) + gapp.Commit() + + return nil +} + +func TestGaiadExport(t *testing.T) { + db := db.NewMemDB() + gapp := NewGaiaApp(log.NewTMLogger(log.NewSyncWriter(os.Stdout)), db, nil, true, 0) + setGenesis(gapp) + + // Making a new app object with the db, so that initchain hasn't been called + newGapp := NewGaiaApp(log.NewTMLogger(log.NewSyncWriter(os.Stdout)), db, nil, true, 0) + _, _, err := newGapp.ExportAppStateAndValidators(false, []string{}) + require.NoError(t, err, "ExportAppStateAndValidators should not have an error") +} diff --git a/gaia/app/benchmarks/txsize_test.go b/gaia/app/benchmarks/txsize_test.go new file mode 100644 index 00000000..af381a3f --- /dev/null +++ b/gaia/app/benchmarks/txsize_test.go @@ -0,0 +1,40 @@ +package app + +import ( + "fmt" + + "github.com/tendermint/tendermint/crypto/secp256k1" + + "github.com/cosmos/cosmos-sdk/cmd/gaia/app" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/auth" + "github.com/cosmos/cosmos-sdk/x/bank" +) + +// 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/gaia/app/export.go similarity index 74% rename from app/export.go rename to gaia/app/export.go index 9cce8f60..2cb110bf 100644 --- a/app/export.go +++ b/gaia/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) { +// export the state of gaia for a genesis file +func (app *GaiaApp) 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,9 +59,7 @@ 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) { +func (app *GaiaApp) prepForZeroHeightGenesis(ctx sdk.Context, jailWhiteList []string) { applyWhiteList := false //Check if there is a whitelist @@ -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/gaia/app/genesis.go b/gaia/app/genesis.go new file mode 100644 index 00000000..997cd131 --- /dev/null +++ b/gaia/app/genesis.go @@ -0,0 +1,417 @@ +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" +) + +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 GaiaAppGenState(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 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, + } +} + +// GaiaValidateGenesisState 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 GaiaValidateGenesisState(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 +} + +// GaiaAppGenState but with JSON +func GaiaAppGenStateJSON(cdc *codec.Codec, genDoc tmtypes.GenesisDoc, appGenTxs []json.RawMessage) ( + appState json.RawMessage, err error) { + // create the final app state + genesisState, err := GaiaAppGenState(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/gaia/app/genesis_test.go b/gaia/app/genesis_test.go new file mode 100644 index 00000000..300f4afc --- /dev/null +++ b/gaia/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 TestGaiaAppGenTx(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 TestGaiaAppGenState(t *testing.T) { + cdc := MakeCodec() + _ = cdc + var genDoc tmtypes.GenesisDoc + + // test unmarshalling error + _, err := GaiaAppGenState(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 = GaiaAppGenState(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 TestGaiaGenesisValidation(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 := GaiaValidateGenesisState(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 = GaiaValidateGenesisState(genesisState) + require.Error(t, err) + genesisState.Accounts[0].StartTime = 1548888000 + genesisState.Accounts[0].EndTime = 1548775410 + err = GaiaValidateGenesisState(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 = GaiaValidateGenesisState(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 = GaiaValidateGenesisState(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, GaiaValidateGenesisState(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/gaia/app/invariants.go b/gaia/app/invariants.go new file mode 100644 index 00000000..899d6bdb --- /dev/null +++ b/gaia/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 *GaiaApp) assertRuntimeInvariants() { + ctx := app.NewContext(false, abci.Header{Height: app.LastBlockHeight() + 1}) + app.assertRuntimeInvariantsOnContext(ctx) +} + +func (app *GaiaApp) 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 gaiacli 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/gaia/app/sim_test.go b/gaia/app/sim_test.go new file mode 100644 index 00000000..28e94e4d --- /dev/null +++ b/gaia/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 *GaiaApp) ( + 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 *GaiaApp) []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 *GaiaApp) []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 := NewGaiaApp(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 := NewGaiaApp(logger, db, nil, true, 0, fauxMerkleModeOpt) + require.Equal(t, "GaiaApp", 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 := NewGaiaApp(logger, db, nil, true, 0, fauxMerkleModeOpt) + require.Equal(t, "GaiaApp", 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 := NewGaiaApp(log.NewNopLogger(), newDB, nil, true, 0, fauxMerkleModeOpt) + require.Equal(t, "GaiaApp", 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 := NewGaiaApp(logger, db, nil, true, 0, fauxMerkleModeOpt) + require.Equal(t, "GaiaApp", 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 := NewGaiaApp(log.NewNopLogger(), newDB, nil, true, 0, fauxMerkleModeOpt) + require.Equal(t, "GaiaApp", 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 := NewGaiaApp(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/gaia/cli_test/README.md b/gaia/cli_test/README.md new file mode 100644 index 00000000..37fd41ce --- /dev/null +++ b/gaia/cli_test/README.md @@ -0,0 +1,51 @@ +# Gaia CLI Integration tests + +The gaia cli integration tests live in this folder. You can run the full suite by running: + +```bash +$ go test -v -p 4 ./cmd/gaia/cli_test/... +# OR! +$ make test_cli +``` +> NOTE: While the full suite runs in parallel, some of the tests can take up to a minute to complete + +### Test Structure + +This integration suite [uses a thin wrapper](https://godoc.org/github.com/cosmos/cosmos-sdk/tests) over the [`os/exec`](https://golang.org/pkg/os/exec/) package. This allows the integration test to run against built binaries (both `gaiad` and `gaiacli` are used) while being written in golang. This allows tests to take advantage of the various golang code we have for operations like marshal/unmarshal, crypto, etc... + +> NOTE: The tests will use whatever `gaiad` or `gaiacli` binaries are available in your `$PATH`. You can check which binary will be run by the suite by running `which gaiad` or `which gaiacli`. If you have your `$GOPATH` properly setup they should be in `$GOPATH/bin/gaia*`. This will ensure that your test uses the latest binary you have built + +Tests generally follow this structure: + +```go +func TestMyNewCommand(t *testing.T) { + t.Parallel() + f := InitFixtures(t) + + // start gaiad server + proc := f.GDStart() + defer proc.Stop(false) + + // Your test code goes here... + + f.Cleanup() +} +``` + +This boilerplate above: +- Ensures the tests run in parallel. Because the tests are calling out to `os/exec` for many operations these tests can take a long time to run. +- Creates `.gaiad` and `.gaiacli` folders in a new temp folder. +- Uses `gaiacli` to create 2 accounts for use in testing: `foo` and `bar` +- Creates a genesis file with coins (`1000footoken,1000feetoken,150stake`) controlled by the `foo` key +- Generates an initial bonding transaction (`gentx`) to make the `foo` key a validator at genesis +- Starts `gaiad` and stops it once the test exits +- Cleans up test state on a successful run + +### Notes when adding/running tests + +- Because the tests run against a built binary, you should make sure you build every time the code changes and you want to test again, otherwise you will be testing against an older version. If you are adding new tests this can easily lead to confusing test results. +- The [`test_helpers.go`](./test_helpers.go) file is organized according to the format of `gaiacli` and `gaiad` commands. There are comments with section headers describing the different areas. Helper functions to call CLI functionality are generally named after the command (e.g. `gaiacli query staking validator` would be `QueryStakingValidator`). Try to keep functions grouped by their position in the command tree. +- Test state that is needed by `tx` and `query` commands (`home`, `chain_id`, etc...) is stored on the `Fixtures` object. This makes constructing your new tests almost trivial. +- Sometimes if you exit a test early there can be still running `gaiad` and `gaiacli` processes that will interrupt subsequent runs. Still running `gaiacli` processes will block access to the keybase while still running `gaiad` processes will block ports and prevent new tests from spinning up. You can ensure new tests spin up clean by running `pkill -9 gaiad && pkill -9 gaiacli` before each test run. +- Most `query` and `tx` commands take a variadic `flags` argument. This pattern allows for the creation of a general function which is easily modified by adding flags. See the `TxSend` function and its use for a good example. +- `Tx*` functions follow a general pattern and return `(success bool, stdout string, stderr string)`. This allows for easy testing of multiple different flag configurations. See `TestGaiaCLICreateValidator` or `TestGaiaCLISubmitProposal` for a good example of the pattern. diff --git a/gaia/cli_test/cli_test.go b/gaia/cli_test/cli_test.go new file mode 100644 index 00000000..8a36a390 --- /dev/null +++ b/gaia/cli_test/cli_test.go @@ -0,0 +1,1093 @@ +// +build cli_test + +package clitest + +import ( + "encoding/base64" + "errors" + "fmt" + "io/ioutil" + "os" + "path" + "path/filepath" + "strings" + "testing" + "time" + + "github.com/tendermint/tendermint/crypto/ed25519" + + "github.com/stretchr/testify/require" + + "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/cmd/gaia/app" + "github.com/cosmos/cosmos-sdk/tests" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/auth" + "github.com/cosmos/cosmos-sdk/x/gov" +) + +func TestGaiaCLIKeysAddMultisig(t *testing.T) { + t.Parallel() + f := InitFixtures(t) + + // key names order does not matter + f.KeysAdd("msig1", "--multisig-threshold=2", + fmt.Sprintf("--multisig=%s,%s", keyBar, keyBaz)) + f.KeysAdd("msig2", "--multisig-threshold=2", + fmt.Sprintf("--multisig=%s,%s", keyBaz, keyBar)) + require.Equal(t, f.KeysShow("msig1").Address, f.KeysShow("msig2").Address) + + f.KeysAdd("msig3", "--multisig-threshold=2", + fmt.Sprintf("--multisig=%s,%s", keyBar, keyBaz), + "--nosort") + f.KeysAdd("msig4", "--multisig-threshold=2", + fmt.Sprintf("--multisig=%s,%s", keyBaz, keyBar), + "--nosort") + require.NotEqual(t, f.KeysShow("msig3").Address, f.KeysShow("msig4").Address) +} + +func TestGaiaCLIKeysAddRecover(t *testing.T) { + t.Parallel() + f := InitFixtures(t) + + exitSuccess, _, _ := f.KeysAddRecover("empty-mnemonic", "") + require.False(t, exitSuccess) + + exitSuccess, _, _ = f.KeysAddRecover("test-recover", "dentist task convince chimney quality leave banana trade firm crawl eternal easily") + require.True(t, exitSuccess) + require.Equal(t, "cosmos1qcfdf69js922qrdr4yaww3ax7gjml6pdds46f4", f.KeyAddress("test-recover").String()) +} + +func TestGaiaCLIKeysAddRecoverHDPath(t *testing.T) { + t.Parallel() + f := InitFixtures(t) + + f.KeysAddRecoverHDPath("test-recoverHD1", "dentist task convince chimney quality leave banana trade firm crawl eternal easily", 0, 0) + require.Equal(t, "cosmos1qcfdf69js922qrdr4yaww3ax7gjml6pdds46f4", f.KeyAddress("test-recoverHD1").String()) + + f.KeysAddRecoverHDPath("test-recoverH2", "dentist task convince chimney quality leave banana trade firm crawl eternal easily", 1, 5) + require.Equal(t, "cosmos1pdfav2cjhry9k79nu6r8kgknnjtq6a7rykmafy", f.KeyAddress("test-recoverH2").String()) + + f.KeysAddRecoverHDPath("test-recoverH3", "dentist task convince chimney quality leave banana trade firm crawl eternal easily", 1, 17) + require.Equal(t, "cosmos1909k354n6wl8ujzu6kmh49w4d02ax7qvlkv4sn", f.KeyAddress("test-recoverH3").String()) + + f.KeysAddRecoverHDPath("test-recoverH4", "dentist task convince chimney quality leave banana trade firm crawl eternal easily", 2, 17) + require.Equal(t, "cosmos1v9plmhvyhgxk3th9ydacm7j4z357s3nhtwsjat", f.KeyAddress("test-recoverH4").String()) +} + +func TestGaiaCLIMinimumFees(t *testing.T) { + t.Parallel() + f := InitFixtures(t) + + // start gaiad server with minimum fees + minGasPrice, _ := sdk.NewDecFromStr("0.000006") + fees := fmt.Sprintf( + "--minimum-gas-prices=%s,%s", + sdk.NewDecCoinFromDec(feeDenom, minGasPrice), + sdk.NewDecCoinFromDec(fee2Denom, minGasPrice), + ) + proc := f.GDStart(fees) + defer proc.Stop(false) + + barAddr := f.KeyAddress(keyBar) + + // Send a transaction that will get rejected + success, _, _ := f.TxSend(keyFoo, barAddr, sdk.NewInt64Coin(fee2Denom, 10), "-y") + require.False(f.T, success) + tests.WaitForNextNBlocksTM(1, f.Port) + + // Ensure tx w/ correct fees pass + txFees := fmt.Sprintf("--fees=%s", sdk.NewInt64Coin(feeDenom, 2)) + success, _, _ = f.TxSend(keyFoo, barAddr, sdk.NewInt64Coin(fee2Denom, 10), txFees, "-y") + require.True(f.T, success) + tests.WaitForNextNBlocksTM(1, f.Port) + + // Ensure tx w/ improper fees fails + txFees = fmt.Sprintf("--fees=%s", sdk.NewInt64Coin(feeDenom, 1)) + success, _, _ = f.TxSend(keyFoo, barAddr, sdk.NewInt64Coin(fooDenom, 10), txFees, "-y") + require.False(f.T, success) + + // Cleanup testing directories + f.Cleanup() +} + +func TestGaiaCLIGasPrices(t *testing.T) { + t.Parallel() + f := InitFixtures(t) + + // start gaiad server with minimum fees + minGasPrice, _ := sdk.NewDecFromStr("0.000006") + proc := f.GDStart(fmt.Sprintf("--minimum-gas-prices=%s", sdk.NewDecCoinFromDec(feeDenom, minGasPrice))) + defer proc.Stop(false) + + barAddr := f.KeyAddress(keyBar) + + // insufficient gas prices (tx fails) + badGasPrice, _ := sdk.NewDecFromStr("0.000003") + success, _, _ := f.TxSend( + keyFoo, barAddr, sdk.NewInt64Coin(fooDenom, 50), + fmt.Sprintf("--gas-prices=%s", sdk.NewDecCoinFromDec(feeDenom, badGasPrice)), "-y") + require.False(t, success) + + // wait for a block confirmation + tests.WaitForNextNBlocksTM(1, f.Port) + + // sufficient gas prices (tx passes) + success, _, _ = f.TxSend( + keyFoo, barAddr, sdk.NewInt64Coin(fooDenom, 50), + fmt.Sprintf("--gas-prices=%s", sdk.NewDecCoinFromDec(feeDenom, minGasPrice)), "-y") + require.True(t, success) + + // wait for a block confirmation + tests.WaitForNextNBlocksTM(1, f.Port) + + f.Cleanup() +} + +func TestGaiaCLIFeesDeduction(t *testing.T) { + t.Parallel() + f := InitFixtures(t) + + // start gaiad server with minimum fees + minGasPrice, _ := sdk.NewDecFromStr("0.000006") + proc := f.GDStart(fmt.Sprintf("--minimum-gas-prices=%s", sdk.NewDecCoinFromDec(feeDenom, minGasPrice))) + defer proc.Stop(false) + + // Save key addresses for later use + fooAddr := f.KeyAddress(keyFoo) + barAddr := f.KeyAddress(keyBar) + + fooAcc := f.QueryAccount(fooAddr) + fooAmt := fooAcc.GetCoins().AmountOf(fooDenom) + + // test simulation + success, _, _ := f.TxSend( + keyFoo, barAddr, sdk.NewInt64Coin(fooDenom, 1000), + fmt.Sprintf("--fees=%s", sdk.NewInt64Coin(feeDenom, 2)), "--dry-run") + require.True(t, success) + + // Wait for a block + tests.WaitForNextNBlocksTM(1, f.Port) + + // ensure state didn't change + fooAcc = f.QueryAccount(fooAddr) + require.Equal(t, fooAmt.Int64(), fooAcc.GetCoins().AmountOf(fooDenom).Int64()) + + // insufficient funds (coins + fees) tx fails + largeCoins := sdk.TokensFromTendermintPower(10000000) + success, _, _ = f.TxSend( + keyFoo, barAddr, sdk.NewCoin(fooDenom, largeCoins), + fmt.Sprintf("--fees=%s", sdk.NewInt64Coin(feeDenom, 2)), "-y") + require.False(t, success) + + // Wait for a block + tests.WaitForNextNBlocksTM(1, f.Port) + + // ensure state didn't change + fooAcc = f.QueryAccount(fooAddr) + require.Equal(t, fooAmt.Int64(), fooAcc.GetCoins().AmountOf(fooDenom).Int64()) + + // test success (transfer = coins + fees) + success, _, _ = f.TxSend( + keyFoo, barAddr, sdk.NewInt64Coin(fooDenom, 500), + fmt.Sprintf("--fees=%s", sdk.NewInt64Coin(feeDenom, 2)), "-y") + require.True(t, success) + + f.Cleanup() +} + +func TestGaiaCLISend(t *testing.T) { + t.Parallel() + f := InitFixtures(t) + + // start gaiad server + proc := f.GDStart() + defer proc.Stop(false) + + // Save key addresses for later use + fooAddr := f.KeyAddress(keyFoo) + barAddr := f.KeyAddress(keyBar) + + fooAcc := f.QueryAccount(fooAddr) + startTokens := sdk.TokensFromTendermintPower(50) + require.Equal(t, startTokens, fooAcc.GetCoins().AmountOf(denom)) + + // Send some tokens from one account to the other + sendTokens := sdk.TokensFromTendermintPower(10) + f.TxSend(keyFoo, barAddr, sdk.NewCoin(denom, sendTokens), "-y") + tests.WaitForNextNBlocksTM(1, f.Port) + + // Ensure account balances match expected + barAcc := f.QueryAccount(barAddr) + require.Equal(t, sendTokens, barAcc.GetCoins().AmountOf(denom)) + fooAcc = f.QueryAccount(fooAddr) + require.Equal(t, startTokens.Sub(sendTokens), fooAcc.GetCoins().AmountOf(denom)) + + // Test --dry-run + success, _, _ := f.TxSend(keyFoo, barAddr, sdk.NewCoin(denom, sendTokens), "--dry-run") + require.True(t, success) + + // Check state didn't change + fooAcc = f.QueryAccount(fooAddr) + require.Equal(t, startTokens.Sub(sendTokens), fooAcc.GetCoins().AmountOf(denom)) + + // test autosequencing + f.TxSend(keyFoo, barAddr, sdk.NewCoin(denom, sendTokens), "-y") + tests.WaitForNextNBlocksTM(1, f.Port) + + // Ensure account balances match expected + barAcc = f.QueryAccount(barAddr) + require.Equal(t, sendTokens.MulRaw(2), barAcc.GetCoins().AmountOf(denom)) + fooAcc = f.QueryAccount(fooAddr) + require.Equal(t, startTokens.Sub(sendTokens.MulRaw(2)), fooAcc.GetCoins().AmountOf(denom)) + + // test memo + f.TxSend(keyFoo, barAddr, sdk.NewCoin(denom, sendTokens), "--memo='testmemo'", "-y") + tests.WaitForNextNBlocksTM(1, f.Port) + + // Ensure account balances match expected + barAcc = f.QueryAccount(barAddr) + require.Equal(t, sendTokens.MulRaw(3), barAcc.GetCoins().AmountOf(denom)) + fooAcc = f.QueryAccount(fooAddr) + require.Equal(t, startTokens.Sub(sendTokens.MulRaw(3)), fooAcc.GetCoins().AmountOf(denom)) + + f.Cleanup() +} + +func TestGaiaCLIConfirmTx(t *testing.T) { + t.Parallel() + f := InitFixtures(t) + + // start gaiad server + proc := f.GDStart() + defer proc.Stop(false) + + // Save key addresses for later use + fooAddr := f.KeyAddress(keyFoo) + barAddr := f.KeyAddress(keyBar) + + fooAcc := f.QueryAccount(fooAddr) + startTokens := sdk.TokensFromTendermintPower(50) + require.Equal(t, startTokens, fooAcc.GetCoins().AmountOf(denom)) + + // send some tokens from one account to the other + sendTokens := sdk.TokensFromTendermintPower(10) + f.txSendWithConfirm(keyFoo, barAddr, sdk.NewCoin(denom, sendTokens), "Y") + tests.WaitForNextNBlocksTM(1, f.Port) + + // ensure account balances match expected + barAcc := f.QueryAccount(barAddr) + require.Equal(t, sendTokens, barAcc.GetCoins().AmountOf(denom)) + + // send some tokens from one account to the other (cancelling confirmation) + f.txSendWithConfirm(keyFoo, barAddr, sdk.NewCoin(denom, sendTokens), "n") + tests.WaitForNextNBlocksTM(1, f.Port) + + // ensure account balances match expected + barAcc = f.QueryAccount(barAddr) + require.Equal(t, sendTokens, barAcc.GetCoins().AmountOf(denom)) +} + +func TestGaiaCLIGasAuto(t *testing.T) { + t.Parallel() + f := InitFixtures(t) + + // start gaiad server + proc := f.GDStart() + defer proc.Stop(false) + + fooAddr := f.KeyAddress(keyFoo) + barAddr := f.KeyAddress(keyBar) + + fooAcc := f.QueryAccount(fooAddr) + startTokens := sdk.TokensFromTendermintPower(50) + require.Equal(t, startTokens, fooAcc.GetCoins().AmountOf(denom)) + + // Test failure with auto gas disabled and very little gas set by hand + sendTokens := sdk.TokensFromTendermintPower(10) + success, _, _ := f.TxSend(keyFoo, barAddr, sdk.NewCoin(denom, sendTokens), "--gas=10", "-y") + require.False(t, success) + + // Check state didn't change + fooAcc = f.QueryAccount(fooAddr) + require.Equal(t, startTokens, fooAcc.GetCoins().AmountOf(denom)) + + // Test failure with negative gas + success, _, _ = f.TxSend(keyFoo, barAddr, sdk.NewCoin(denom, sendTokens), "--gas=-100", "-y") + require.False(t, success) + + // Check state didn't change + fooAcc = f.QueryAccount(fooAddr) + require.Equal(t, startTokens, fooAcc.GetCoins().AmountOf(denom)) + + // Test failure with 0 gas + success, _, _ = f.TxSend(keyFoo, barAddr, sdk.NewCoin(denom, sendTokens), "--gas=0", "-y") + require.False(t, success) + + // Check state didn't change + fooAcc = f.QueryAccount(fooAddr) + require.Equal(t, startTokens, fooAcc.GetCoins().AmountOf(denom)) + + // Enable auto gas + success, stdout, stderr := f.TxSend(keyFoo, barAddr, sdk.NewCoin(denom, sendTokens), "--gas=auto", "-y") + require.NotEmpty(t, stderr) + require.True(t, success) + cdc := app.MakeCodec() + sendResp := sdk.TxResponse{} + err := cdc.UnmarshalJSON([]byte(stdout), &sendResp) + require.Nil(t, err) + require.True(t, sendResp.GasWanted >= sendResp.GasUsed) + tests.WaitForNextNBlocksTM(1, f.Port) + + // Check state has changed accordingly + fooAcc = f.QueryAccount(fooAddr) + require.Equal(t, startTokens.Sub(sendTokens), fooAcc.GetCoins().AmountOf(denom)) + + f.Cleanup() +} + +func TestGaiaCLICreateValidator(t *testing.T) { + t.Parallel() + f := InitFixtures(t) + + // start gaiad server + proc := f.GDStart() + defer proc.Stop(false) + + barAddr := f.KeyAddress(keyBar) + barVal := sdk.ValAddress(barAddr) + + consPubKey := sdk.MustBech32ifyConsPub(ed25519.GenPrivKey().PubKey()) + + sendTokens := sdk.TokensFromTendermintPower(10) + f.TxSend(keyFoo, barAddr, sdk.NewCoin(denom, sendTokens), "-y") + tests.WaitForNextNBlocksTM(1, f.Port) + + barAcc := f.QueryAccount(barAddr) + require.Equal(t, sendTokens, barAcc.GetCoins().AmountOf(denom)) + + // Generate a create validator transaction and ensure correctness + success, stdout, stderr := f.TxStakingCreateValidator(barAddr.String(), consPubKey, sdk.NewInt64Coin(denom, 2), "--generate-only") + + require.True(f.T, success) + require.Empty(f.T, stderr) + msg := unmarshalStdTx(f.T, stdout) + require.NotZero(t, msg.Fee.Gas) + require.Equal(t, len(msg.Msgs), 1) + require.Equal(t, 0, len(msg.GetSignatures())) + + // Test --dry-run + newValTokens := sdk.TokensFromTendermintPower(2) + success, _, _ = f.TxStakingCreateValidator(keyBar, consPubKey, sdk.NewCoin(denom, newValTokens), "--dry-run") + require.True(t, success) + + // Create the validator + f.TxStakingCreateValidator(keyBar, consPubKey, sdk.NewCoin(denom, newValTokens), "-y") + tests.WaitForNextNBlocksTM(1, f.Port) + + // Ensure funds were deducted properly + barAcc = f.QueryAccount(barAddr) + require.Equal(t, sendTokens.Sub(newValTokens), barAcc.GetCoins().AmountOf(denom)) + + // Ensure that validator state is as expected + validator := f.QueryStakingValidator(barVal) + require.Equal(t, validator.OperatorAddress, barVal) + require.True(sdk.IntEq(t, newValTokens, validator.Tokens)) + + // Query delegations to the validator + validatorDelegations := f.QueryStakingDelegationsTo(barVal) + require.Len(t, validatorDelegations, 1) + require.NotZero(t, validatorDelegations[0].Shares) + + // unbond a single share + unbondAmt := sdk.NewCoin(sdk.DefaultBondDenom, sdk.TokensFromTendermintPower(1)) + success = f.TxStakingUnbond(keyBar, unbondAmt.String(), barVal, "-y") + require.True(t, success) + tests.WaitForNextNBlocksTM(1, f.Port) + + // Ensure bonded staking is correct + remainingTokens := newValTokens.Sub(unbondAmt.Amount) + validator = f.QueryStakingValidator(barVal) + require.Equal(t, remainingTokens, validator.Tokens) + + // Get unbonding delegations from the validator + validatorUbds := f.QueryStakingUnbondingDelegationsFrom(barVal) + require.Len(t, validatorUbds, 1) + require.Len(t, validatorUbds[0].Entries, 1) + require.Equal(t, remainingTokens.String(), validatorUbds[0].Entries[0].Balance.String()) + + f.Cleanup() +} + +func TestGaiaCLISubmitProposal(t *testing.T) { + t.Parallel() + f := InitFixtures(t) + + // start gaiad server + proc := f.GDStart() + defer proc.Stop(false) + + f.QueryGovParamDeposit() + f.QueryGovParamVoting() + f.QueryGovParamTallying() + + fooAddr := f.KeyAddress(keyFoo) + + fooAcc := f.QueryAccount(fooAddr) + startTokens := sdk.TokensFromTendermintPower(50) + require.Equal(t, startTokens, fooAcc.GetCoins().AmountOf(sdk.DefaultBondDenom)) + + proposalsQuery := f.QueryGovProposals() + require.Empty(t, proposalsQuery) + + // Test submit generate only for submit proposal + proposalTokens := sdk.TokensFromTendermintPower(5) + success, stdout, stderr := f.TxGovSubmitProposal( + fooAddr.String(), "Text", "Test", "test", sdk.NewCoin(denom, proposalTokens), "--generate-only", "-y") + require.True(t, success) + require.Empty(t, stderr) + msg := unmarshalStdTx(t, stdout) + require.NotZero(t, msg.Fee.Gas) + require.Equal(t, len(msg.Msgs), 1) + require.Equal(t, 0, len(msg.GetSignatures())) + + // Test --dry-run + success, _, _ = f.TxGovSubmitProposal(keyFoo, "Text", "Test", "test", sdk.NewCoin(denom, proposalTokens), "--dry-run") + require.True(t, success) + + // Create the proposal + f.TxGovSubmitProposal(keyFoo, "Text", "Test", "test", sdk.NewCoin(denom, proposalTokens), "-y") + tests.WaitForNextNBlocksTM(1, f.Port) + + // Ensure transaction tags can be queried + txs := f.QueryTxs(1, 50, "action:submit_proposal", fmt.Sprintf("proposer:%s", fooAddr)) + require.Len(t, txs, 1) + + // Ensure deposit was deducted + fooAcc = f.QueryAccount(fooAddr) + require.Equal(t, startTokens.Sub(proposalTokens), fooAcc.GetCoins().AmountOf(denom)) + + // Ensure propsal is directly queryable + proposal1 := f.QueryGovProposal(1) + require.Equal(t, uint64(1), proposal1.ProposalID) + require.Equal(t, gov.StatusDepositPeriod, proposal1.Status) + + // Ensure query proposals returns properly + proposalsQuery = f.QueryGovProposals() + require.Equal(t, uint64(1), proposalsQuery[0].ProposalID) + + // Query the deposits on the proposal + deposit := f.QueryGovDeposit(1, fooAddr) + require.Equal(t, proposalTokens, deposit.Amount.AmountOf(denom)) + + // Test deposit generate only + depositTokens := sdk.TokensFromTendermintPower(10) + success, stdout, stderr = f.TxGovDeposit(1, fooAddr.String(), sdk.NewCoin(denom, depositTokens), "--generate-only") + require.True(t, success) + require.Empty(t, stderr) + msg = unmarshalStdTx(t, stdout) + require.NotZero(t, msg.Fee.Gas) + require.Equal(t, len(msg.Msgs), 1) + require.Equal(t, 0, len(msg.GetSignatures())) + + // Run the deposit transaction + f.TxGovDeposit(1, keyFoo, sdk.NewCoin(denom, depositTokens), "-y") + tests.WaitForNextNBlocksTM(1, f.Port) + + // test query deposit + deposits := f.QueryGovDeposits(1) + require.Len(t, deposits, 1) + require.Equal(t, proposalTokens.Add(depositTokens), deposits[0].Amount.AmountOf(denom)) + + // Ensure querying the deposit returns the proper amount + deposit = f.QueryGovDeposit(1, fooAddr) + require.Equal(t, proposalTokens.Add(depositTokens), deposit.Amount.AmountOf(denom)) + + // Ensure tags are set on the transaction + txs = f.QueryTxs(1, 50, "action:deposit", fmt.Sprintf("depositor:%s", fooAddr)) + require.Len(t, txs, 1) + + // Ensure account has expected amount of funds + fooAcc = f.QueryAccount(fooAddr) + require.Equal(t, startTokens.Sub(proposalTokens.Add(depositTokens)), fooAcc.GetCoins().AmountOf(denom)) + + // Fetch the proposal and ensure it is now in the voting period + proposal1 = f.QueryGovProposal(1) + require.Equal(t, uint64(1), proposal1.ProposalID) + require.Equal(t, gov.StatusVotingPeriod, proposal1.Status) + + // Test vote generate only + success, stdout, stderr = f.TxGovVote(1, gov.OptionYes, fooAddr.String(), "--generate-only") + require.True(t, success) + require.Empty(t, stderr) + msg = unmarshalStdTx(t, stdout) + require.NotZero(t, msg.Fee.Gas) + require.Equal(t, len(msg.Msgs), 1) + require.Equal(t, 0, len(msg.GetSignatures())) + + // Vote on the proposal + f.TxGovVote(1, gov.OptionYes, keyFoo, "-y") + tests.WaitForNextNBlocksTM(1, f.Port) + + // Query the vote + vote := f.QueryGovVote(1, fooAddr) + require.Equal(t, uint64(1), vote.ProposalID) + require.Equal(t, gov.OptionYes, vote.Option) + + // Query the votes + votes := f.QueryGovVotes(1) + require.Len(t, votes, 1) + require.Equal(t, uint64(1), votes[0].ProposalID) + require.Equal(t, gov.OptionYes, votes[0].Option) + + // Ensure tags are applied to voting transaction properly + txs = f.QueryTxs(1, 50, "action:vote", fmt.Sprintf("voter:%s", fooAddr)) + require.Len(t, txs, 1) + + // Ensure no proposals in deposit period + proposalsQuery = f.QueryGovProposals("--status=DepositPeriod") + require.Empty(t, proposalsQuery) + + // Ensure the proposal returns as in the voting period + proposalsQuery = f.QueryGovProposals("--status=VotingPeriod") + require.Equal(t, uint64(1), proposalsQuery[0].ProposalID) + + // submit a second test proposal + f.TxGovSubmitProposal(keyFoo, "Text", "Apples", "test", sdk.NewCoin(denom, proposalTokens), "-y") + tests.WaitForNextNBlocksTM(1, f.Port) + + // Test limit on proposals query + proposalsQuery = f.QueryGovProposals("--limit=1") + require.Equal(t, uint64(2), proposalsQuery[0].ProposalID) + + f.Cleanup() +} + +func TestGaiaCLIQueryTxPagination(t *testing.T) { + t.Parallel() + f := InitFixtures(t) + + // start gaiad server + proc := f.GDStart() + defer proc.Stop(false) + + fooAddr := f.KeyAddress(keyFoo) + barAddr := f.KeyAddress(keyBar) + + accFoo := f.QueryAccount(fooAddr) + seq := accFoo.GetSequence() + + for i := 1; i <= 30; i++ { + success, _, _ := f.TxSend(keyFoo, barAddr, sdk.NewInt64Coin(fooDenom, int64(i)), fmt.Sprintf("--sequence=%d", seq), "-y") + require.True(t, success) + seq++ + } + + // perPage = 15, 2 pages + txsPage1 := f.QueryTxs(1, 15, fmt.Sprintf("sender:%s", fooAddr)) + require.Len(t, txsPage1, 15) + txsPage2 := f.QueryTxs(2, 15, fmt.Sprintf("sender:%s", fooAddr)) + require.Len(t, txsPage2, 15) + require.NotEqual(t, txsPage1, txsPage2) + txsPage3 := f.QueryTxs(3, 15, fmt.Sprintf("sender:%s", fooAddr)) + require.Len(t, txsPage3, 15) + require.Equal(t, txsPage2, txsPage3) + + // perPage = 16, 2 pages + txsPage1 = f.QueryTxs(1, 16, fmt.Sprintf("sender:%s", fooAddr)) + require.Len(t, txsPage1, 16) + txsPage2 = f.QueryTxs(2, 16, fmt.Sprintf("sender:%s", fooAddr)) + require.Len(t, txsPage2, 14) + require.NotEqual(t, txsPage1, txsPage2) + + // perPage = 50 + txsPageFull := f.QueryTxs(1, 50, fmt.Sprintf("sender:%s", fooAddr)) + require.Len(t, txsPageFull, 30) + require.Equal(t, txsPageFull, append(txsPage1, txsPage2...)) + + // perPage = 0 + f.QueryTxsInvalid(errors.New("ERROR: page must greater than 0"), 0, 50, fmt.Sprintf("sender:%s", fooAddr)) + + // limit = 0 + f.QueryTxsInvalid(errors.New("ERROR: limit must greater than 0"), 1, 0, fmt.Sprintf("sender:%s", fooAddr)) +} + +func TestGaiaCLIValidateSignatures(t *testing.T) { + t.Parallel() + f := InitFixtures(t) + + // start gaiad server + proc := f.GDStart() + defer proc.Stop(false) + + fooAddr := f.KeyAddress(keyFoo) + barAddr := f.KeyAddress(keyBar) + + // generate sendTx with default gas + success, stdout, stderr := f.TxSend(fooAddr.String(), barAddr, sdk.NewInt64Coin(denom, 10), "--generate-only") + require.True(t, success) + require.Empty(t, stderr) + + // write unsigned tx to file + unsignedTxFile := WriteToNewTempFile(t, stdout) + defer os.Remove(unsignedTxFile.Name()) + + // validate we can successfully sign + success, stdout, stderr = f.TxSign(keyFoo, unsignedTxFile.Name()) + require.True(t, success) + require.Empty(t, stderr) + stdTx := unmarshalStdTx(t, stdout) + require.Equal(t, len(stdTx.Msgs), 1) + require.Equal(t, 1, len(stdTx.GetSignatures())) + require.Equal(t, fooAddr.String(), stdTx.GetSigners()[0].String()) + + // write signed tx to file + signedTxFile := WriteToNewTempFile(t, stdout) + defer os.Remove(signedTxFile.Name()) + + // validate signatures + success, _, _ = f.TxSign(keyFoo, signedTxFile.Name(), "--validate-signatures") + require.True(t, success) + + // modify the transaction + stdTx.Memo = "MODIFIED-ORIGINAL-TX-BAD" + bz := marshalStdTx(t, stdTx) + modSignedTxFile := WriteToNewTempFile(t, string(bz)) + defer os.Remove(modSignedTxFile.Name()) + + // validate signature validation failure due to different transaction sig bytes + success, _, _ = f.TxSign(keyFoo, modSignedTxFile.Name(), "--validate-signatures") + require.False(t, success) + + f.Cleanup() +} + +func TestGaiaCLISendGenerateSignAndBroadcast(t *testing.T) { + t.Parallel() + f := InitFixtures(t) + + // start gaiad server + proc := f.GDStart() + defer proc.Stop(false) + + fooAddr := f.KeyAddress(keyFoo) + barAddr := f.KeyAddress(keyBar) + + // Test generate sendTx with default gas + sendTokens := sdk.TokensFromTendermintPower(10) + success, stdout, stderr := f.TxSend(fooAddr.String(), barAddr, sdk.NewCoin(denom, sendTokens), "--generate-only") + require.True(t, success) + require.Empty(t, stderr) + msg := unmarshalStdTx(t, stdout) + require.Equal(t, msg.Fee.Gas, uint64(client.DefaultGasLimit)) + require.Equal(t, len(msg.Msgs), 1) + require.Equal(t, 0, len(msg.GetSignatures())) + + // Test generate sendTx with --gas=$amount + success, stdout, stderr = f.TxSend(fooAddr.String(), barAddr, sdk.NewCoin(denom, sendTokens), "--gas=100", "--generate-only") + require.True(t, success) + require.Empty(t, stderr) + msg = unmarshalStdTx(t, stdout) + require.Equal(t, msg.Fee.Gas, uint64(100)) + require.Equal(t, len(msg.Msgs), 1) + require.Equal(t, 0, len(msg.GetSignatures())) + + // Test generate sendTx, estimate gas + success, stdout, stderr = f.TxSend(fooAddr.String(), barAddr, sdk.NewCoin(denom, sendTokens), "--gas=auto", "--generate-only") + require.True(t, success) + require.NotEmpty(t, stderr) + msg = unmarshalStdTx(t, stdout) + require.True(t, msg.Fee.Gas > 0) + require.Equal(t, len(msg.Msgs), 1) + + // Write the output to disk + unsignedTxFile := WriteToNewTempFile(t, stdout) + defer os.Remove(unsignedTxFile.Name()) + + // Test sign --validate-signatures + success, stdout, _ = f.TxSign(keyFoo, unsignedTxFile.Name(), "--validate-signatures") + require.False(t, success) + require.Equal(t, fmt.Sprintf("Signers:\n 0: %v\n\nSignatures:\n\n", fooAddr.String()), stdout) + + // Test sign + success, stdout, _ = f.TxSign(keyFoo, unsignedTxFile.Name()) + require.True(t, success) + msg = unmarshalStdTx(t, stdout) + require.Equal(t, len(msg.Msgs), 1) + require.Equal(t, 1, len(msg.GetSignatures())) + require.Equal(t, fooAddr.String(), msg.GetSigners()[0].String()) + + // Write the output to disk + signedTxFile := WriteToNewTempFile(t, stdout) + defer os.Remove(signedTxFile.Name()) + + // Test sign --validate-signatures + success, stdout, _ = f.TxSign(keyFoo, signedTxFile.Name(), "--validate-signatures") + require.True(t, success) + require.Equal(t, fmt.Sprintf("Signers:\n 0: %v\n\nSignatures:\n 0: %v\t\t\t[OK]\n\n", fooAddr.String(), + fooAddr.String()), stdout) + + // Ensure foo has right amount of funds + fooAcc := f.QueryAccount(fooAddr) + startTokens := sdk.TokensFromTendermintPower(50) + require.Equal(t, startTokens, fooAcc.GetCoins().AmountOf(denom)) + + // Test broadcast + success, stdout, _ = f.TxBroadcast(signedTxFile.Name()) + require.True(t, success) + + var result sdk.TxResponse + + // Unmarshal the response and ensure that gas was properly used + require.Nil(t, app.MakeCodec().UnmarshalJSON([]byte(stdout), &result)) + require.Equal(t, msg.Fee.Gas, uint64(result.GasUsed)) + require.Equal(t, msg.Fee.Gas, uint64(result.GasWanted)) + tests.WaitForNextNBlocksTM(1, f.Port) + + // Ensure account state + barAcc := f.QueryAccount(barAddr) + fooAcc = f.QueryAccount(fooAddr) + require.Equal(t, sendTokens, barAcc.GetCoins().AmountOf(denom)) + require.Equal(t, startTokens.Sub(sendTokens), fooAcc.GetCoins().AmountOf(denom)) + + f.Cleanup() +} + +func TestGaiaCLIMultisignInsufficientCosigners(t *testing.T) { + t.Parallel() + f := InitFixtures(t) + + // start gaiad server with minimum fees + proc := f.GDStart() + defer proc.Stop(false) + + fooBarBazAddr := f.KeyAddress(keyFooBarBaz) + barAddr := f.KeyAddress(keyBar) + + // Send some tokens from one account to the other + success, _, _ := f.TxSend(keyFoo, fooBarBazAddr, sdk.NewInt64Coin(denom, 10), "-y") + require.True(t, success) + tests.WaitForNextNBlocksTM(1, f.Port) + + // Test generate sendTx with multisig + success, stdout, _ := f.TxSend(fooBarBazAddr.String(), barAddr, sdk.NewInt64Coin(denom, 5), "--generate-only") + require.True(t, success) + + // Write the output to disk + unsignedTxFile := WriteToNewTempFile(t, stdout) + defer os.Remove(unsignedTxFile.Name()) + + // Sign with foo's key + success, stdout, _ = f.TxSign(keyFoo, unsignedTxFile.Name(), "--multisig", fooBarBazAddr.String(), "-y") + require.True(t, success) + + // Write the output to disk + fooSignatureFile := WriteToNewTempFile(t, stdout) + defer os.Remove(fooSignatureFile.Name()) + + // Multisign, not enough signatures + success, stdout, _ = f.TxMultisign(unsignedTxFile.Name(), keyFooBarBaz, []string{fooSignatureFile.Name()}) + require.True(t, success) + + // Write the output to disk + signedTxFile := WriteToNewTempFile(t, stdout) + defer os.Remove(signedTxFile.Name()) + + // Validate the multisignature + success, _, _ = f.TxSign(keyFooBarBaz, signedTxFile.Name(), "--validate-signatures") + require.False(t, success) + + // Broadcast the transaction + success, _, _ = f.TxBroadcast(signedTxFile.Name()) + require.False(t, success) +} + +func TestGaiaCLIEncode(t *testing.T) { + t.Parallel() + f := InitFixtures(t) + + // start gaiad server + proc := f.GDStart() + defer proc.Stop(false) + + cdc := app.MakeCodec() + + // Build a testing transaction and write it to disk + barAddr := f.KeyAddress(keyBar) + keyAddr := f.KeyAddress(keyFoo) + + sendTokens := sdk.TokensFromTendermintPower(10) + success, stdout, stderr := f.TxSend(keyAddr.String(), barAddr, sdk.NewCoin(denom, sendTokens), "--generate-only", "--memo", "deadbeef") + require.True(t, success) + require.Empty(t, stderr) + + // Write it to disk + jsonTxFile := WriteToNewTempFile(t, stdout) + defer os.Remove(jsonTxFile.Name()) + + // Run the encode command, and trim the extras from the stdout capture + success, base64Encoded, _ := f.TxEncode(jsonTxFile.Name()) + require.True(t, success) + trimmedBase64 := strings.Trim(base64Encoded, "\"\n") + + // Decode the base64 + decodedBytes, err := base64.StdEncoding.DecodeString(trimmedBase64) + require.Nil(t, err) + + // Check that the transaction decodes as epxceted + var decodedTx auth.StdTx + require.Nil(t, cdc.UnmarshalBinaryLengthPrefixed(decodedBytes, &decodedTx)) + require.Equal(t, "deadbeef", decodedTx.Memo) +} + +func TestGaiaCLIMultisignSortSignatures(t *testing.T) { + t.Parallel() + f := InitFixtures(t) + + // start gaiad server with minimum fees + proc := f.GDStart() + defer proc.Stop(false) + + fooBarBazAddr := f.KeyAddress(keyFooBarBaz) + barAddr := f.KeyAddress(keyBar) + + // Send some tokens from one account to the other + success, _, _ := f.TxSend(keyFoo, fooBarBazAddr, sdk.NewInt64Coin(denom, 10), "-y") + require.True(t, success) + tests.WaitForNextNBlocksTM(1, f.Port) + + // Ensure account balances match expected + fooBarBazAcc := f.QueryAccount(fooBarBazAddr) + require.Equal(t, int64(10), fooBarBazAcc.GetCoins().AmountOf(denom).Int64()) + + // Test generate sendTx with multisig + success, stdout, _ := f.TxSend(fooBarBazAddr.String(), barAddr, sdk.NewInt64Coin(denom, 5), "--generate-only") + require.True(t, success) + + // Write the output to disk + unsignedTxFile := WriteToNewTempFile(t, stdout) + defer os.Remove(unsignedTxFile.Name()) + + // Sign with foo's key + success, stdout, _ = f.TxSign(keyFoo, unsignedTxFile.Name(), "--multisig", fooBarBazAddr.String()) + require.True(t, success) + + // Write the output to disk + fooSignatureFile := WriteToNewTempFile(t, stdout) + defer os.Remove(fooSignatureFile.Name()) + + // Sign with baz's key + success, stdout, _ = f.TxSign(keyBaz, unsignedTxFile.Name(), "--multisig", fooBarBazAddr.String()) + require.True(t, success) + + // Write the output to disk + bazSignatureFile := WriteToNewTempFile(t, stdout) + defer os.Remove(bazSignatureFile.Name()) + + // Multisign, keys in different order + success, stdout, _ = f.TxMultisign(unsignedTxFile.Name(), keyFooBarBaz, []string{ + bazSignatureFile.Name(), fooSignatureFile.Name()}) + require.True(t, success) + + // Write the output to disk + signedTxFile := WriteToNewTempFile(t, stdout) + defer os.Remove(signedTxFile.Name()) + + // Validate the multisignature + success, _, _ = f.TxSign(keyFooBarBaz, signedTxFile.Name(), "--validate-signatures") + require.True(t, success) + + // Broadcast the transaction + success, _, _ = f.TxBroadcast(signedTxFile.Name()) + require.True(t, success) +} + +func TestGaiaCLIMultisign(t *testing.T) { + t.Parallel() + f := InitFixtures(t) + + // start gaiad server with minimum fees + proc := f.GDStart() + defer proc.Stop(false) + + fooBarBazAddr := f.KeyAddress(keyFooBarBaz) + bazAddr := f.KeyAddress(keyBaz) + + // Send some tokens from one account to the other + success, _, _ := f.TxSend(keyFoo, fooBarBazAddr, sdk.NewInt64Coin(denom, 10), "-y") + require.True(t, success) + tests.WaitForNextNBlocksTM(1, f.Port) + + // Ensure account balances match expected + fooBarBazAcc := f.QueryAccount(fooBarBazAddr) + require.Equal(t, int64(10), fooBarBazAcc.GetCoins().AmountOf(denom).Int64()) + + // Test generate sendTx with multisig + success, stdout, stderr := f.TxSend(fooBarBazAddr.String(), bazAddr, sdk.NewInt64Coin(denom, 10), "--generate-only") + require.True(t, success) + require.Empty(t, stderr) + + // Write the output to disk + unsignedTxFile := WriteToNewTempFile(t, stdout) + defer os.Remove(unsignedTxFile.Name()) + + // Sign with foo's key + success, stdout, _ = f.TxSign(keyFoo, unsignedTxFile.Name(), "--multisig", fooBarBazAddr.String(), "-y") + require.True(t, success) + + // Write the output to disk + fooSignatureFile := WriteToNewTempFile(t, stdout) + defer os.Remove(fooSignatureFile.Name()) + + // Sign with bar's key + success, stdout, _ = f.TxSign(keyBar, unsignedTxFile.Name(), "--multisig", fooBarBazAddr.String(), "-y") + require.True(t, success) + + // Write the output to disk + barSignatureFile := WriteToNewTempFile(t, stdout) + defer os.Remove(barSignatureFile.Name()) + + // Multisign + success, stdout, _ = f.TxMultisign(unsignedTxFile.Name(), keyFooBarBaz, []string{ + fooSignatureFile.Name(), barSignatureFile.Name()}) + require.True(t, success) + + // Write the output to disk + signedTxFile := WriteToNewTempFile(t, stdout) + defer os.Remove(signedTxFile.Name()) + + // Validate the multisignature + success, _, _ = f.TxSign(keyFooBarBaz, signedTxFile.Name(), "--validate-signatures", "-y") + require.True(t, success) + + // Broadcast the transaction + success, _, _ = f.TxBroadcast(signedTxFile.Name()) + require.True(t, success) +} + +func TestGaiaCLIConfig(t *testing.T) { + t.Parallel() + f := InitFixtures(t) + node := fmt.Sprintf("%s:%s", f.RPCAddr, f.Port) + + // Set available configuration options + f.CLIConfig("broadcast-mode", "block") + f.CLIConfig("node", node) + f.CLIConfig("output", "text") + f.CLIConfig("trust-node", "true") + f.CLIConfig("chain-id", f.ChainID) + f.CLIConfig("trace", "false") + f.CLIConfig("indent", "true") + + config, err := ioutil.ReadFile(path.Join(f.GCLIHome, "config", "config.toml")) + require.NoError(t, err) + expectedConfig := fmt.Sprintf(`broadcast-mode = "block" +chain-id = "%s" +indent = true +node = "%s" +output = "text" +trace = false +trust-node = true +`, f.ChainID, node) + require.Equal(t, expectedConfig, string(config)) + + f.Cleanup() +} + +func TestGaiadCollectGentxs(t *testing.T) { + t.Parallel() + f := NewFixtures(t) + + // Initialise temporary directories + gentxDir, err := ioutil.TempDir("", "") + gentxDoc := filepath.Join(gentxDir, "gentx.json") + require.NoError(t, err) + + // Reset testing path + f.UnsafeResetAll() + + // Initialize keys + f.KeysAdd(keyFoo) + + // Configure json output + f.CLIConfig("output", "json") + + // Run init + f.GDInit(keyFoo) + + // Add account to genesis.json + f.AddGenesisAccount(f.KeyAddress(keyFoo), startCoins) + + // Write gentx file + f.GenTx(keyFoo, fmt.Sprintf("--output-document=%s", gentxDoc)) + + // Collect gentxs from a custom directory + f.CollectGenTxs(fmt.Sprintf("--gentx-dir=%s", gentxDir)) + + f.Cleanup(gentxDir) +} + +func TestGaiadAddGenesisAccount(t *testing.T) { + t.Parallel() + f := NewFixtures(t) + + // Reset testing path + f.UnsafeResetAll() + + // Initialize keys + f.KeysDelete(keyFoo) + f.KeysDelete(keyBar) + f.KeysDelete(keyBaz) + f.KeysAdd(keyFoo) + f.KeysAdd(keyBar) + f.KeysAdd(keyBaz) + + // Configure json output + f.CLIConfig("output", "json") + + // Run init + f.GDInit(keyFoo) + + // Add account to genesis.json + bazCoins := sdk.Coins{ + sdk.NewInt64Coin("acoin", 1000000), + sdk.NewInt64Coin("bcoin", 1000000), + } + + f.AddGenesisAccount(f.KeyAddress(keyFoo), startCoins) + f.AddGenesisAccount(f.KeyAddress(keyBar), bazCoins) + genesisState := f.GenesisState() + require.Equal(t, genesisState.Accounts[0].Address, f.KeyAddress(keyFoo)) + require.Equal(t, genesisState.Accounts[1].Address, f.KeyAddress(keyBar)) + require.True(t, genesisState.Accounts[0].Coins.IsEqual(startCoins)) + require.True(t, genesisState.Accounts[1].Coins.IsEqual(bazCoins)) +} + +func TestSlashingGetParams(t *testing.T) { + t.Parallel() + f := InitFixtures(t) + + // start gaiad server + proc := f.GDStart() + defer proc.Stop(false) + + params := f.QuerySlashingParams() + require.Equal(t, time.Duration(120000000000), params.MaxEvidenceAge) + require.Equal(t, int64(100), params.SignedBlocksWindow) + require.Equal(t, sdk.NewDecWithPrec(5, 1), params.MinSignedPerWindow) + + sinfo := f.QuerySigningInfo(f.GDTendermint("show-validator")) + require.Equal(t, int64(0), sinfo.StartHeight) + require.False(t, sinfo.Tombstoned) +} + +func TestValidateGenesis(t *testing.T) { + t.Parallel() + f := InitFixtures(t) + + // start gaiad server + proc := f.GDStart() + defer proc.Stop(false) + + f.ValidateGenesis() +} diff --git a/gaia/cli_test/doc.go b/gaia/cli_test/doc.go new file mode 100644 index 00000000..bcf9c5e4 --- /dev/null +++ b/gaia/cli_test/doc.go @@ -0,0 +1,3 @@ +package clitest + +// package clitest runs integration tests which make use of CLI commands. diff --git a/gaia/cli_test/test_helpers.go b/gaia/cli_test/test_helpers.go new file mode 100644 index 00000000..a05e967e --- /dev/null +++ b/gaia/cli_test/test_helpers.go @@ -0,0 +1,685 @@ +package clitest + +import ( + "encoding/json" + "fmt" + "io/ioutil" + "os" + "path/filepath" + "strings" + "testing" + "time" + + "github.com/stretchr/testify/require" + + cmn "github.com/tendermint/tendermint/libs/common" + + clientkeys "github.com/cosmos/cosmos-sdk/client/keys" + "github.com/cosmos/cosmos-sdk/cmd/gaia/app" + appInit "github.com/cosmos/cosmos-sdk/cmd/gaia/init" + "github.com/cosmos/cosmos-sdk/codec" + "github.com/cosmos/cosmos-sdk/crypto/keys" + "github.com/cosmos/cosmos-sdk/server" + "github.com/cosmos/cosmos-sdk/tests" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/auth" + "github.com/cosmos/cosmos-sdk/x/gov" + "github.com/cosmos/cosmos-sdk/x/slashing" + "github.com/cosmos/cosmos-sdk/x/staking" +) + +const ( + denom = "stake" + keyFoo = "foo" + keyBar = "bar" + fooDenom = "footoken" + feeDenom = "feetoken" + fee2Denom = "fee2token" + keyBaz = "baz" + keyVesting = "vesting" + keyFooBarBaz = "foobarbaz" +) + +var ( + startCoins = sdk.Coins{ + sdk.NewCoin(feeDenom, sdk.TokensFromTendermintPower(1000000)), + sdk.NewCoin(fee2Denom, sdk.TokensFromTendermintPower(1000000)), + sdk.NewCoin(fooDenom, sdk.TokensFromTendermintPower(1000)), + sdk.NewCoin(denom, sdk.TokensFromTendermintPower(150)), + } + + vestingCoins = sdk.Coins{ + sdk.NewCoin(feeDenom, sdk.TokensFromTendermintPower(500000)), + } +) + +//___________________________________________________________________________________ +// Fixtures + +// Fixtures is used to setup the testing environment +type Fixtures struct { + ChainID string + RPCAddr string + Port string + GDHome string + GCLIHome string + P2PAddr string + T *testing.T +} + +// NewFixtures creates a new instance of Fixtures with many vars set +func NewFixtures(t *testing.T) *Fixtures { + tmpDir, err := ioutil.TempDir("", "gaia_integration_"+t.Name()+"_") + require.NoError(t, err) + + servAddr, port, err := server.FreeTCPAddr() + require.NoError(t, err) + + p2pAddr, _, err := server.FreeTCPAddr() + require.NoError(t, err) + + return &Fixtures{ + T: t, + GDHome: filepath.Join(tmpDir, ".gaiad"), + GCLIHome: filepath.Join(tmpDir, ".gaiacli"), + RPCAddr: servAddr, + P2PAddr: p2pAddr, + Port: port, + } +} + +// GenesisFile returns the path of the genesis file +func (f Fixtures) GenesisFile() string { + return filepath.Join(f.GDHome, "config", "genesis.json") +} + +// GenesisFile returns the application's genesis state +func (f Fixtures) GenesisState() app.GenesisState { + cdc := codec.New() + genDoc, err := appInit.LoadGenesisDoc(cdc, f.GenesisFile()) + require.NoError(f.T, err) + + var appState app.GenesisState + require.NoError(f.T, cdc.UnmarshalJSON(genDoc.AppState, &appState)) + return appState +} + +// InitFixtures is called at the beginning of a test and initializes a chain +// with 1 validator. +func InitFixtures(t *testing.T) (f *Fixtures) { + f = NewFixtures(t) + + // reset test state + f.UnsafeResetAll() + + // ensure keystore has foo and bar keys + f.KeysDelete(keyFoo) + f.KeysDelete(keyBar) + f.KeysDelete(keyBar) + f.KeysDelete(keyFooBarBaz) + f.KeysAdd(keyFoo) + f.KeysAdd(keyBar) + f.KeysAdd(keyBaz) + f.KeysAdd(keyVesting) + f.KeysAdd(keyFooBarBaz, "--multisig-threshold=2", fmt.Sprintf( + "--multisig=%s,%s,%s", keyFoo, keyBar, keyBaz)) + + // ensure that CLI output is in JSON format + f.CLIConfig("output", "json") + + // NOTE: GDInit sets the ChainID + f.GDInit(keyFoo) + + f.CLIConfig("chain-id", f.ChainID) + f.CLIConfig("broadcast-mode", "block") + + // start an account with tokens + f.AddGenesisAccount(f.KeyAddress(keyFoo), startCoins) + f.AddGenesisAccount( + f.KeyAddress(keyVesting), startCoins, + fmt.Sprintf("--vesting-amount=%s", vestingCoins), + fmt.Sprintf("--vesting-start-time=%d", time.Now().UTC().UnixNano()), + fmt.Sprintf("--vesting-end-time=%d", time.Now().Add(60*time.Second).UTC().UnixNano()), + ) + + f.GenTx(keyFoo) + f.CollectGenTxs() + + return +} + +// Cleanup is meant to be run at the end of a test to clean up an remaining test state +func (f *Fixtures) Cleanup(dirs ...string) { + clean := append(dirs, f.GDHome, f.GCLIHome) + for _, d := range clean { + err := os.RemoveAll(d) + require.NoError(f.T, err) + } +} + +// Flags returns the flags necessary for making most CLI calls +func (f *Fixtures) Flags() string { + return fmt.Sprintf("--home=%s --node=%s", f.GCLIHome, f.RPCAddr) +} + +//___________________________________________________________________________________ +// gaiad + +// UnsafeResetAll is gaiad unsafe-reset-all +func (f *Fixtures) UnsafeResetAll(flags ...string) { + cmd := fmt.Sprintf("../../../build/gaiad --home=%s unsafe-reset-all", f.GDHome) + executeWrite(f.T, addFlags(cmd, flags)) + err := os.RemoveAll(filepath.Join(f.GDHome, "config", "gentx")) + require.NoError(f.T, err) +} + +// GDInit is gaiad init +// NOTE: GDInit sets the ChainID for the Fixtures instance +func (f *Fixtures) GDInit(moniker string, flags ...string) { + cmd := fmt.Sprintf("../../../build/gaiad init -o --home=%s %s", f.GDHome, moniker) + _, stderr := tests.ExecuteT(f.T, addFlags(cmd, flags), app.DefaultKeyPass) + + var chainID string + var initRes map[string]json.RawMessage + + err := json.Unmarshal([]byte(stderr), &initRes) + require.NoError(f.T, err) + + err = json.Unmarshal(initRes["chain_id"], &chainID) + require.NoError(f.T, err) + + f.ChainID = chainID +} + +// AddGenesisAccount is gaiad add-genesis-account +func (f *Fixtures) AddGenesisAccount(address sdk.AccAddress, coins sdk.Coins, flags ...string) { + cmd := fmt.Sprintf("../../../build/gaiad add-genesis-account %s %s --home=%s", address, coins, f.GDHome) + executeWriteCheckErr(f.T, addFlags(cmd, flags)) +} + +// GenTx is gaiad gentx +func (f *Fixtures) GenTx(name string, flags ...string) { + cmd := fmt.Sprintf("../../../build/gaiad gentx --name=%s --home=%s --home-client=%s", name, f.GDHome, f.GCLIHome) + executeWriteCheckErr(f.T, addFlags(cmd, flags), app.DefaultKeyPass) +} + +// CollectGenTxs is gaiad collect-gentxs +func (f *Fixtures) CollectGenTxs(flags ...string) { + cmd := fmt.Sprintf("../../../build/gaiad collect-gentxs --home=%s", f.GDHome) + executeWriteCheckErr(f.T, addFlags(cmd, flags), app.DefaultKeyPass) +} + +// GDStart runs gaiad start with the appropriate flags and returns a process +func (f *Fixtures) GDStart(flags ...string) *tests.Process { + cmd := fmt.Sprintf("../../../build/gaiad start --home=%s --rpc.laddr=%v --p2p.laddr=%v", f.GDHome, f.RPCAddr, f.P2PAddr) + proc := tests.GoExecuteTWithStdout(f.T, addFlags(cmd, flags)) + tests.WaitForTMStart(f.Port) + tests.WaitForNextNBlocksTM(1, f.Port) + return proc +} + +// GDTendermint returns the results of gaiad tendermint [query] +func (f *Fixtures) GDTendermint(query string) string { + cmd := fmt.Sprintf("../../../build/gaiad tendermint %s --home=%s", query, f.GDHome) + success, stdout, stderr := executeWriteRetStdStreams(f.T, cmd) + require.Empty(f.T, stderr) + require.True(f.T, success) + return strings.TrimSpace(stdout) +} + +// ValidateGenesis runs gaiad validate-genesis +func (f *Fixtures) ValidateGenesis() { + cmd := fmt.Sprintf("../../../build/gaiad validate-genesis --home=%s", f.GDHome) + executeWriteCheckErr(f.T, cmd) +} + +//___________________________________________________________________________________ +// gaiacli keys + +// KeysDelete is gaiacli keys delete +func (f *Fixtures) KeysDelete(name string, flags ...string) { + cmd := fmt.Sprintf("../../../build/gaiacli keys delete --home=%s %s", f.GCLIHome, name) + executeWrite(f.T, addFlags(cmd, append(append(flags, "-y"), "-f"))) +} + +// KeysAdd is gaiacli keys add +func (f *Fixtures) KeysAdd(name string, flags ...string) { + cmd := fmt.Sprintf("../../../build/gaiacli keys add --home=%s %s", f.GCLIHome, name) + executeWriteCheckErr(f.T, addFlags(cmd, flags), app.DefaultKeyPass) +} + +// KeysAddRecover prepares gaiacli keys add --recover +func (f *Fixtures) KeysAddRecover(name, mnemonic string, flags ...string) (exitSuccess bool, stdout, stderr string) { + cmd := fmt.Sprintf("../../../build/gaiacli keys add --home=%s --recover %s", f.GCLIHome, name) + return executeWriteRetStdStreams(f.T, addFlags(cmd, flags), app.DefaultKeyPass, mnemonic) +} + +// KeysAddRecoverHDPath prepares gaiacli keys add --recover --account --index +func (f *Fixtures) KeysAddRecoverHDPath(name, mnemonic string, account uint32, index uint32, flags ...string) { + cmd := fmt.Sprintf("../../../build/gaiacli keys add --home=%s --recover %s --account %d --index %d", f.GCLIHome, name, account, index) + executeWriteCheckErr(f.T, addFlags(cmd, flags), app.DefaultKeyPass, mnemonic) +} + +// KeysShow is gaiacli keys show +func (f *Fixtures) KeysShow(name string, flags ...string) keys.KeyOutput { + cmd := fmt.Sprintf("../../../build/gaiacli keys show --home=%s %s", f.GCLIHome, name) + out, _ := tests.ExecuteT(f.T, addFlags(cmd, flags), "") + var ko keys.KeyOutput + err := clientkeys.UnmarshalJSON([]byte(out), &ko) + require.NoError(f.T, err) + return ko +} + +// KeyAddress returns the SDK account address from the key +func (f *Fixtures) KeyAddress(name string) sdk.AccAddress { + ko := f.KeysShow(name) + accAddr, err := sdk.AccAddressFromBech32(ko.Address) + require.NoError(f.T, err) + return accAddr +} + +//___________________________________________________________________________________ +// gaiacli config + +// CLIConfig is gaiacli config +func (f *Fixtures) CLIConfig(key, value string, flags ...string) { + cmd := fmt.Sprintf("../../../build/gaiacli config --home=%s %s %s", f.GCLIHome, key, value) + executeWriteCheckErr(f.T, addFlags(cmd, flags)) +} + +//___________________________________________________________________________________ +// gaiacli tx send/sign/broadcast + +// TxSend is gaiacli tx send +func (f *Fixtures) TxSend(from string, to sdk.AccAddress, amount sdk.Coin, flags ...string) (bool, string, string) { + cmd := fmt.Sprintf("../../../build/gaiacli tx send %s %s %v --from=%s", to, amount, f.Flags(), from) + return executeWriteRetStdStreams(f.T, addFlags(cmd, flags), app.DefaultKeyPass) +} + +func (f *Fixtures) txSendWithConfirm( + from string, to sdk.AccAddress, amount sdk.Coin, confirm string, flags ...string, +) (bool, string, string) { + + cmd := fmt.Sprintf("../../../build/gaiacli tx send %s %s %v --from=%s", to, amount, f.Flags(), from) + return executeWriteRetStdStreams(f.T, addFlags(cmd, flags), confirm, app.DefaultKeyPass) +} + +// TxSign is gaiacli tx sign +func (f *Fixtures) TxSign(signer, fileName string, flags ...string) (bool, string, string) { + cmd := fmt.Sprintf("../../../build/gaiacli tx sign %v --from=%s %v", f.Flags(), signer, fileName) + return executeWriteRetStdStreams(f.T, addFlags(cmd, flags), app.DefaultKeyPass) +} + +// TxBroadcast is gaiacli tx broadcast +func (f *Fixtures) TxBroadcast(fileName string, flags ...string) (bool, string, string) { + cmd := fmt.Sprintf("../../../build/gaiacli tx broadcast %v %v", f.Flags(), fileName) + return executeWriteRetStdStreams(f.T, addFlags(cmd, flags), app.DefaultKeyPass) +} + +// TxEncode is gaiacli tx encode +func (f *Fixtures) TxEncode(fileName string, flags ...string) (bool, string, string) { + cmd := fmt.Sprintf("../../../build/gaiacli tx encode %v %v", f.Flags(), fileName) + return executeWriteRetStdStreams(f.T, addFlags(cmd, flags), app.DefaultKeyPass) +} + +// TxMultisign is gaiacli tx multisign +func (f *Fixtures) TxMultisign(fileName, name string, signaturesFiles []string, + flags ...string) (bool, string, string) { + + cmd := fmt.Sprintf("../../../build/gaiacli tx multisign %v %s %s %s", f.Flags(), + fileName, name, strings.Join(signaturesFiles, " "), + ) + return executeWriteRetStdStreams(f.T, cmd) +} + +//___________________________________________________________________________________ +// gaiacli tx staking + +// TxStakingCreateValidator is gaiacli tx staking create-validator +func (f *Fixtures) TxStakingCreateValidator(from, consPubKey string, amount sdk.Coin, flags ...string) (bool, string, string) { + cmd := fmt.Sprintf("../../../build/gaiacli tx staking create-validator %v --from=%s --pubkey=%s", f.Flags(), from, consPubKey) + cmd += fmt.Sprintf(" --amount=%v --moniker=%v --commission-rate=%v", amount, from, "0.05") + cmd += fmt.Sprintf(" --commission-max-rate=%v --commission-max-change-rate=%v", "0.20", "0.10") + cmd += fmt.Sprintf(" --min-self-delegation=%v", "1") + return executeWriteRetStdStreams(f.T, addFlags(cmd, flags), app.DefaultKeyPass) +} + +// TxStakingUnbond is gaiacli tx staking unbond +func (f *Fixtures) TxStakingUnbond(from, shares string, validator sdk.ValAddress, flags ...string) bool { + cmd := fmt.Sprintf("../../../build/gaiacli tx staking unbond %s %v --from=%s %v", validator, shares, from, f.Flags()) + return executeWrite(f.T, addFlags(cmd, flags), app.DefaultKeyPass) +} + +//___________________________________________________________________________________ +// gaiacli tx gov + +// TxGovSubmitProposal is gaiacli tx gov submit-proposal +func (f *Fixtures) TxGovSubmitProposal(from, typ, title, description string, deposit sdk.Coin, flags ...string) (bool, string, string) { + cmd := fmt.Sprintf("../../../build/gaiacli tx gov submit-proposal %v --from=%s --type=%s", f.Flags(), from, typ) + cmd += fmt.Sprintf(" --title=%s --description=%s --deposit=%s", title, description, deposit) + return executeWriteRetStdStreams(f.T, addFlags(cmd, flags), app.DefaultKeyPass) +} + +// TxGovDeposit is gaiacli tx gov deposit +func (f *Fixtures) TxGovDeposit(proposalID int, from string, amount sdk.Coin, flags ...string) (bool, string, string) { + cmd := fmt.Sprintf("../../../build/gaiacli tx gov deposit %d %s --from=%s %v", proposalID, amount, from, f.Flags()) + return executeWriteRetStdStreams(f.T, addFlags(cmd, flags), app.DefaultKeyPass) +} + +// TxGovVote is gaiacli tx gov vote +func (f *Fixtures) TxGovVote(proposalID int, option gov.VoteOption, from string, flags ...string) (bool, string, string) { + cmd := fmt.Sprintf("../../../build/gaiacli tx gov vote %d %s --from=%s %v", proposalID, option, from, f.Flags()) + return executeWriteRetStdStreams(f.T, addFlags(cmd, flags), app.DefaultKeyPass) +} + +//___________________________________________________________________________________ +// gaiacli query account + +// QueryAccount is gaiacli query account +func (f *Fixtures) QueryAccount(address sdk.AccAddress, flags ...string) auth.BaseAccount { + cmd := fmt.Sprintf("../../../build/gaiacli query account %s %v", address, f.Flags()) + out, _ := tests.ExecuteT(f.T, addFlags(cmd, flags), "") + var initRes map[string]json.RawMessage + err := json.Unmarshal([]byte(out), &initRes) + require.NoError(f.T, err, "out %v, err %v", out, err) + value := initRes["value"] + var acc auth.BaseAccount + cdc := codec.New() + codec.RegisterCrypto(cdc) + err = cdc.UnmarshalJSON(value, &acc) + require.NoError(f.T, err, "value %v, err %v", string(value), err) + return acc +} + +//___________________________________________________________________________________ +// gaiacli query txs + +// QueryTxs is gaiacli query txs +func (f *Fixtures) QueryTxs(page, limit int, tags ...string) []sdk.TxResponse { + cmd := fmt.Sprintf("../../../build/gaiacli query txs --page=%d --limit=%d --tags='%s' %v", page, limit, queryTags(tags), f.Flags()) + out, _ := tests.ExecuteT(f.T, cmd, "") + var txs []sdk.TxResponse + cdc := app.MakeCodec() + err := cdc.UnmarshalJSON([]byte(out), &txs) + require.NoError(f.T, err, "out %v\n, err %v", out, err) + return txs +} + +// QueryTxsInvalid query txs with wrong parameters and compare expected error +func (f *Fixtures) QueryTxsInvalid(expectedErr error, page, limit int, tags ...string) { + cmd := fmt.Sprintf("../../../build/gaiacli query txs --page=%d --limit=%d --tags='%s' %v", page, limit, queryTags(tags), f.Flags()) + _, err := tests.ExecuteT(f.T, cmd, "") + require.EqualError(f.T, expectedErr, err) +} + +//___________________________________________________________________________________ +// gaiacli query staking + +// QueryStakingValidator is gaiacli query staking validator +func (f *Fixtures) QueryStakingValidator(valAddr sdk.ValAddress, flags ...string) staking.Validator { + cmd := fmt.Sprintf("../../../build/gaiacli query staking validator %s %v", valAddr, f.Flags()) + out, _ := tests.ExecuteT(f.T, addFlags(cmd, flags), "") + var validator staking.Validator + cdc := app.MakeCodec() + err := cdc.UnmarshalJSON([]byte(out), &validator) + require.NoError(f.T, err, "out %v\n, err %v", out, err) + return validator +} + +// QueryStakingUnbondingDelegationsFrom is gaiacli query staking unbonding-delegations-from +func (f *Fixtures) QueryStakingUnbondingDelegationsFrom(valAddr sdk.ValAddress, flags ...string) []staking.UnbondingDelegation { + cmd := fmt.Sprintf("../../../build/gaiacli query staking unbonding-delegations-from %s %v", valAddr, f.Flags()) + out, _ := tests.ExecuteT(f.T, addFlags(cmd, flags), "") + var ubds []staking.UnbondingDelegation + cdc := app.MakeCodec() + err := cdc.UnmarshalJSON([]byte(out), &ubds) + require.NoError(f.T, err, "out %v\n, err %v", out, err) + return ubds +} + +// QueryStakingDelegationsTo is gaiacli query staking delegations-to +func (f *Fixtures) QueryStakingDelegationsTo(valAddr sdk.ValAddress, flags ...string) []staking.Delegation { + cmd := fmt.Sprintf("../../../build/gaiacli query staking delegations-to %s %v", valAddr, f.Flags()) + out, _ := tests.ExecuteT(f.T, addFlags(cmd, flags), "") + var delegations []staking.Delegation + cdc := app.MakeCodec() + err := cdc.UnmarshalJSON([]byte(out), &delegations) + require.NoError(f.T, err, "out %v\n, err %v", out, err) + return delegations +} + +// QueryStakingPool is gaiacli query staking pool +func (f *Fixtures) QueryStakingPool(flags ...string) staking.Pool { + cmd := fmt.Sprintf("../../../build/gaiacli query staking pool %v", f.Flags()) + out, _ := tests.ExecuteT(f.T, addFlags(cmd, flags), "") + var pool staking.Pool + cdc := app.MakeCodec() + err := cdc.UnmarshalJSON([]byte(out), &pool) + require.NoError(f.T, err, "out %v\n, err %v", out, err) + return pool +} + +// QueryStakingParameters is gaiacli query staking parameters +func (f *Fixtures) QueryStakingParameters(flags ...string) staking.Params { + cmd := fmt.Sprintf("../../../build/gaiacli query staking params %v", f.Flags()) + out, _ := tests.ExecuteT(f.T, addFlags(cmd, flags), "") + var params staking.Params + cdc := app.MakeCodec() + err := cdc.UnmarshalJSON([]byte(out), ¶ms) + require.NoError(f.T, err, "out %v\n, err %v", out, err) + return params +} + +//___________________________________________________________________________________ +// gaiacli query gov + +// QueryGovParamDeposit is gaiacli query gov param deposit +func (f *Fixtures) QueryGovParamDeposit() gov.DepositParams { + cmd := fmt.Sprintf("../../../build/gaiacli query gov param deposit %s", f.Flags()) + out, _ := tests.ExecuteT(f.T, cmd, "") + var depositParam gov.DepositParams + cdc := app.MakeCodec() + err := cdc.UnmarshalJSON([]byte(out), &depositParam) + require.NoError(f.T, err, "out %v\n, err %v", out, err) + return depositParam +} + +// QueryGovParamVoting is gaiacli query gov param voting +func (f *Fixtures) QueryGovParamVoting() gov.VotingParams { + cmd := fmt.Sprintf("../../../build/gaiacli query gov param voting %s", f.Flags()) + out, _ := tests.ExecuteT(f.T, cmd, "") + var votingParam gov.VotingParams + cdc := app.MakeCodec() + err := cdc.UnmarshalJSON([]byte(out), &votingParam) + require.NoError(f.T, err, "out %v\n, err %v", out, err) + return votingParam +} + +// QueryGovParamTallying is gaiacli query gov param tallying +func (f *Fixtures) QueryGovParamTallying() gov.TallyParams { + cmd := fmt.Sprintf("../../../build/gaiacli query gov param tallying %s", f.Flags()) + out, _ := tests.ExecuteT(f.T, cmd, "") + var tallyingParam gov.TallyParams + cdc := app.MakeCodec() + err := cdc.UnmarshalJSON([]byte(out), &tallyingParam) + require.NoError(f.T, err, "out %v\n, err %v", out, err) + return tallyingParam +} + +// QueryGovProposals is gaiacli query gov proposals +func (f *Fixtures) QueryGovProposals(flags ...string) gov.Proposals { + cmd := fmt.Sprintf("../../../build/gaiacli query gov proposals %v", f.Flags()) + stdout, stderr := tests.ExecuteT(f.T, addFlags(cmd, flags), "") + if strings.Contains(stderr, "No matching proposals found") { + return gov.Proposals{} + } + require.Empty(f.T, stderr) + var out gov.Proposals + cdc := app.MakeCodec() + err := cdc.UnmarshalJSON([]byte(stdout), &out) + require.NoError(f.T, err) + return out +} + +// QueryGovProposal is gaiacli query gov proposal +func (f *Fixtures) QueryGovProposal(proposalID int, flags ...string) gov.Proposal { + cmd := fmt.Sprintf("../../../build/gaiacli query gov proposal %d %v", proposalID, f.Flags()) + out, _ := tests.ExecuteT(f.T, addFlags(cmd, flags), "") + var proposal gov.Proposal + cdc := app.MakeCodec() + err := cdc.UnmarshalJSON([]byte(out), &proposal) + require.NoError(f.T, err, "out %v\n, err %v", out, err) + return proposal +} + +// QueryGovVote is gaiacli query gov vote +func (f *Fixtures) QueryGovVote(proposalID int, voter sdk.AccAddress, flags ...string) gov.Vote { + cmd := fmt.Sprintf("../../../build/gaiacli query gov vote %d %s %v", proposalID, voter, f.Flags()) + out, _ := tests.ExecuteT(f.T, addFlags(cmd, flags), "") + var vote gov.Vote + cdc := app.MakeCodec() + err := cdc.UnmarshalJSON([]byte(out), &vote) + require.NoError(f.T, err, "out %v\n, err %v", out, err) + return vote +} + +// QueryGovVotes is gaiacli query gov votes +func (f *Fixtures) QueryGovVotes(proposalID int, flags ...string) []gov.Vote { + cmd := fmt.Sprintf("../../../build/gaiacli query gov votes %d %v", proposalID, f.Flags()) + out, _ := tests.ExecuteT(f.T, addFlags(cmd, flags), "") + var votes []gov.Vote + cdc := app.MakeCodec() + err := cdc.UnmarshalJSON([]byte(out), &votes) + require.NoError(f.T, err, "out %v\n, err %v", out, err) + return votes +} + +// QueryGovDeposit is gaiacli query gov deposit +func (f *Fixtures) QueryGovDeposit(proposalID int, depositor sdk.AccAddress, flags ...string) gov.Deposit { + cmd := fmt.Sprintf("../../../build/gaiacli query gov deposit %d %s %v", proposalID, depositor, f.Flags()) + out, _ := tests.ExecuteT(f.T, addFlags(cmd, flags), "") + var deposit gov.Deposit + cdc := app.MakeCodec() + err := cdc.UnmarshalJSON([]byte(out), &deposit) + require.NoError(f.T, err, "out %v\n, err %v", out, err) + return deposit +} + +// QueryGovDeposits is gaiacli query gov deposits +func (f *Fixtures) QueryGovDeposits(propsalID int, flags ...string) []gov.Deposit { + cmd := fmt.Sprintf("../../../build/gaiacli query gov deposits %d %v", propsalID, f.Flags()) + out, _ := tests.ExecuteT(f.T, addFlags(cmd, flags), "") + var deposits []gov.Deposit + cdc := app.MakeCodec() + err := cdc.UnmarshalJSON([]byte(out), &deposits) + require.NoError(f.T, err, "out %v\n, err %v", out, err) + return deposits +} + +//___________________________________________________________________________________ +// query slashing + +// QuerySigningInfo returns the signing info for a validator +func (f *Fixtures) QuerySigningInfo(val string) slashing.ValidatorSigningInfo { + cmd := fmt.Sprintf("../../../build/gaiacli query slashing signing-info %s %s", val, f.Flags()) + res, errStr := tests.ExecuteT(f.T, cmd, "") + require.Empty(f.T, errStr) + cdc := app.MakeCodec() + var sinfo slashing.ValidatorSigningInfo + err := cdc.UnmarshalJSON([]byte(res), &sinfo) + require.NoError(f.T, err) + return sinfo +} + +// QuerySlashingParams is gaiacli query slashing params +func (f *Fixtures) QuerySlashingParams() slashing.Params { + cmd := fmt.Sprintf("../../../build/gaiacli query slashing params %s", f.Flags()) + res, errStr := tests.ExecuteT(f.T, cmd, "") + require.Empty(f.T, errStr) + cdc := app.MakeCodec() + var params slashing.Params + err := cdc.UnmarshalJSON([]byte(res), ¶ms) + require.NoError(f.T, err) + return params +} + +//___________________________________________________________________________________ +// executors + +func executeWriteCheckErr(t *testing.T, cmdStr string, writes ...string) { + require.True(t, executeWrite(t, cmdStr, writes...)) +} + +func executeWrite(t *testing.T, cmdStr string, writes ...string) (exitSuccess bool) { + exitSuccess, _, _ = executeWriteRetStdStreams(t, cmdStr, writes...) + return +} + +func executeWriteRetStdStreams(t *testing.T, cmdStr string, writes ...string) (bool, string, string) { + proc := tests.GoExecuteT(t, cmdStr) + + // Enables use of interactive commands + for _, write := range writes { + _, err := proc.StdinPipe.Write([]byte(write + "\n")) + require.NoError(t, err) + } + + // Read both stdout and stderr from the process + stdout, stderr, err := proc.ReadAll() + if err != nil { + fmt.Println("Err on proc.ReadAll()", err, cmdStr) + } + + // Log output. + if len(stdout) > 0 { + t.Log("Stdout:", cmn.Green(string(stdout))) + } + if len(stderr) > 0 { + t.Log("Stderr:", cmn.Red(string(stderr))) + } + + // Wait for process to exit + proc.Wait() + + // Return succes, stdout, stderr + return proc.ExitState.Success(), string(stdout), string(stderr) +} + +//___________________________________________________________________________________ +// utils + +func addFlags(cmd string, flags []string) string { + for _, f := range flags { + cmd += " " + f + } + return strings.TrimSpace(cmd) +} + +func queryTags(tags []string) (out string) { + for _, tag := range tags { + out += tag + "&" + } + return strings.TrimSuffix(out, "&") +} + +// Write the given string to a new temporary file +func WriteToNewTempFile(t *testing.T, s string) *os.File { + fp, err := ioutil.TempFile(os.TempDir(), "cosmos_cli_test_") + require.Nil(t, err) + _, err = fp.WriteString(s) + require.Nil(t, err) + return fp +} + +func marshalStdTx(t *testing.T, stdTx auth.StdTx) []byte { + cdc := app.MakeCodec() + bz, err := cdc.MarshalBinaryBare(stdTx) + require.NoError(t, err) + return bz +} + +func unmarshalStdTx(t *testing.T, s string) (stdTx auth.StdTx) { + cdc := app.MakeCodec() + require.Nil(t, cdc.UnmarshalJSON([]byte(s), &stdTx)) + return +} diff --git a/cmd/kvcli/main.go b/gaia/cmd/gaiacli/main.go similarity index 51% rename from cmd/kvcli/main.go rename to gaia/cmd/gaiacli/main.go index ddc13ff4..4fd6d1c3 100644 --- a/cmd/kvcli/main.go +++ b/gaia/cmd/gaiacli/main.go @@ -1,32 +1,52 @@ -// Copyright 2016 All in Bits, inc -// Modifications copyright 2019 Kava Labs - package main import ( "fmt" + "net/http" "os" "path" + "github.com/cosmos/cosmos-sdk/x/mint" + + "github.com/rakyll/statik/fs" + "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" "github.com/cosmos/cosmos-sdk/client/rpc" "github.com/cosmos/cosmos-sdk/client/tx" + "github.com/cosmos/cosmos-sdk/cmd/gaia/app" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/version" - "github.com/cosmos/cosmos-sdk/x/auth" + + 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" + 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" - "github.com/spf13/cobra" - "github.com/spf13/viper" - - "github.com/tendermint/go-amino" - "github.com/tendermint/tendermint/libs/cli" - - "github.com/kava-labs/_/app" + _ "github.com/cosmos/cosmos-sdk/client/lcd/statik" ) func main() { @@ -38,16 +58,29 @@ func main() { // Read in the configuration file for the sdk config := sdk.GetConfig() - app.SetAddressPrefixes(config) + config.SetBech32PrefixForAccount(sdk.Bech32PrefixAccAddr, sdk.Bech32PrefixAccPub) + config.SetBech32PrefixForValidator(sdk.Bech32PrefixValAddr, sdk.Bech32PrefixValPub) + config.SetBech32PrefixForConsensusNode(sdk.Bech32PrefixConsAddr, sdk.Bech32PrefixConsPub) 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", + Use: "gaiacli", + Short: "Command line interface for interacting with gaiad", } // Add --chain-id to persistent flags and mark it required @@ -60,14 +93,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 +114,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 +122,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 +151,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 +167,25 @@ 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) + registerSwaggerUI(rs) + 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 registerSwaggerUI(rs *lcd.RestServer) { + statikFS, err := fs.New() + if err != nil { + panic(err) + } + staticServer := http.FileServer(statikFS) + rs.Mux.PathPrefix("/swagger-ui/").Handler(http.StripPrefix("/swagger-ui/", staticServer)) } func initConfig(cmd *cobra.Command) error { diff --git a/cmd/kvd/main.go b/gaia/cmd/gaiad/main.go similarity index 53% rename from cmd/kvd/main.go rename to gaia/cmd/gaiad/main.go index 14c1e72d..216b8df6 100644 --- a/cmd/kvd/main.go +++ b/gaia/cmd/gaiad/main.go @@ -1,6 +1,3 @@ -// Copyright 2016 All in Bits, inc -// Modifications copyright 2019 Kava Labs - package main import ( @@ -10,22 +7,19 @@ 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/cmd/gaia/app" + gaiaInit "github.com/cosmos/cosmos-sdk/cmd/gaia/init" + "github.com/cosmos/cosmos-sdk/server" + "github.com/cosmos/cosmos-sdk/store" + sdk "github.com/cosmos/cosmos-sdk/types" ) // gaiad custom flags @@ -37,45 +31,45 @@ func main() { cdc := app.MakeCodec() config := sdk.GetConfig() - app.SetAddressPrefixes(config) + config.SetBech32PrefixForAccount(sdk.Bech32PrefixAccAddr, sdk.Bech32PrefixAccPub) + config.SetBech32PrefixForValidator(sdk.Bech32PrefixValAddr, sdk.Bech32PrefixValPub) + config.SetBech32PrefixForConsensusNode(sdk.Bech32PrefixConsAddr, sdk.Bech32PrefixConsPub) config.Seal() ctx := server.NewDefaultContext() cobra.EnableCommandSorting = false rootCmd := &cobra.Command{ - Use: "kvd", - Short: "Kava Daemon (server)", + Use: "gaiad", + Short: "Gaia Daemon (server)", 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(gaiaInit.InitCmd(ctx, cdc)) + rootCmd.AddCommand(gaiaInit.CollectGenTxsCmd(ctx, cdc)) + rootCmd.AddCommand(gaiaInit.TestnetFilesCmd(ctx, cdc)) + rootCmd.AddCommand(gaiaInit.GenTxCmd(ctx, cdc)) + rootCmd.AddCommand(gaiaInit.AddGenesisAccountCmd(ctx, cdc)) + rootCmd.AddCommand(gaiaInit.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) } } func newApp(logger log.Logger, db dbm.DB, traceStore io.Writer) abci.Application { - return app.NewApp( + return app.NewGaiaApp( 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.NewGaiaApp(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.NewGaiaApp(logger, db, traceStore, true, uint(1)) + return gApp.ExportAppStateAndValidators(forZeroHeight, jailWhiteList) } diff --git a/gaia/cmd/gaiadebug/README.md b/gaia/cmd/gaiadebug/README.md new file mode 100644 index 00000000..c2f0b8bc --- /dev/null +++ b/gaia/cmd/gaiadebug/README.md @@ -0,0 +1,35 @@ +# Gaiadebug + +Simple tool for simple debugging. + +We try to accept both hex and base64 formats and provide a useful response. + +Note we often encode bytes as hex in the logs, but as base64 in the JSON. + +## Pubkeys + +The following give the same result: + +``` +gaiadebug pubkey TZTQnfqOsi89SeoXVnIw+tnFJnr4X8qVC0U8AsEmFk4= +gaiadebug pubkey 4D94D09DFA8EB22F3D49EA17567230FAD9C5267AF85FCA950B453C02C126164E +``` + +## Txs + +Pass in a hex/base64 tx and get back the full JSON + +``` +gaiadebug tx +``` + +## Hack + +This is a command with boilerplate for using Go as a scripting language to hack +on an existing Gaia state. + +Currently we have an example for the state of gaia-6001 after it +[crashed](https://github.com/cosmos/cosmos-sdk/blob/master/cmd/gaia/testnets/STATUS.md#june-13-2018-230-est---published-postmortem-of-gaia-6001-failure). +If you run `gaiadebug hack $HOME/.gaiad` on that +state, it will do a binary search on the state history to find when the state +invariant was violated. diff --git a/gaia/cmd/gaiadebug/hack.go b/gaia/cmd/gaiadebug/hack.go new file mode 100644 index 00000000..51927592 --- /dev/null +++ b/gaia/cmd/gaiadebug/hack.go @@ -0,0 +1,268 @@ +package main + +import ( + "encoding/base64" + "encoding/hex" + "fmt" + "os" + "path" + + "github.com/cosmos/cosmos-sdk/store" + + "github.com/cosmos/cosmos-sdk/baseapp" + + "github.com/spf13/cobra" + "github.com/spf13/viper" + abci "github.com/tendermint/tendermint/abci/types" + "github.com/tendermint/tendermint/crypto/ed25519" + + cmn "github.com/tendermint/tendermint/libs/common" + dbm "github.com/tendermint/tendermint/libs/db" + "github.com/tendermint/tendermint/libs/log" + + bam "github.com/cosmos/cosmos-sdk/baseapp" + sdk "github.com/cosmos/cosmos-sdk/types" + + "github.com/cosmos/cosmos-sdk/codec" + "github.com/cosmos/cosmos-sdk/x/auth" + "github.com/cosmos/cosmos-sdk/x/bank" + "github.com/cosmos/cosmos-sdk/x/params" + "github.com/cosmos/cosmos-sdk/x/slashing" + "github.com/cosmos/cosmos-sdk/x/staking" + + gaia "github.com/cosmos/cosmos-sdk/cmd/gaia/app" +) + +func runHackCmd(cmd *cobra.Command, args []string) error { + + if len(args) != 1 { + return fmt.Errorf("Expected 1 arg") + } + + // ".gaiad" + dataDir := args[0] + dataDir = path.Join(dataDir, "data") + + // load the app + logger := log.NewTMLogger(log.NewSyncWriter(os.Stdout)) + db, err := sdk.NewLevelDB("gaia", dataDir) + if err != nil { + fmt.Println(err) + os.Exit(1) + } + app := NewGaiaApp(logger, db, baseapp.SetPruning(store.NewPruningOptionsFromString(viper.GetString("pruning")))) + + // print some info + id := app.LastCommitID() + lastBlockHeight := app.LastBlockHeight() + fmt.Println("ID", id) + fmt.Println("LastBlockHeight", lastBlockHeight) + + //---------------------------------------------------- + // XXX: start hacking! + //---------------------------------------------------- + // eg. gaia-6001 testnet bug + // We paniced when iterating through the "bypower" keys. + // The following powerKey was there, but the corresponding "trouble" validator did not exist. + // So here we do a binary search on the past states to find when the powerKey first showed up ... + + // operator of the validator the bonds, gets jailed, later unbonds, and then later is still found in the bypower store + trouble := hexToBytes("D3DC0FF59F7C3B548B7AFA365561B87FD0208AF8") + // this is his "bypower" key + powerKey := hexToBytes("05303030303030303030303033FFFFFFFFFFFF4C0C0000FFFED3DC0FF59F7C3B548B7AFA365561B87FD0208AF8") + + topHeight := lastBlockHeight + bottomHeight := int64(0) + checkHeight := topHeight + for { + // load the given version of the state + err = app.LoadVersion(checkHeight, app.keyMain) + if err != nil { + fmt.Println(err) + os.Exit(1) + } + ctx := app.NewContext(true, abci.Header{}) + + // check for the powerkey and the validator from the store + store := ctx.KVStore(app.keyStaking) + res := store.Get(powerKey) + val, _ := app.stakingKeeper.GetValidator(ctx, trouble) + fmt.Println("checking height", checkHeight, res, val) + if res == nil { + bottomHeight = checkHeight + } else { + topHeight = checkHeight + } + checkHeight = (topHeight + bottomHeight) / 2 + } +} + +func base64ToPub(b64 string) ed25519.PubKeyEd25519 { + data, _ := base64.StdEncoding.DecodeString(b64) + var pubKey ed25519.PubKeyEd25519 + copy(pubKey[:], data) + return pubKey + +} + +func hexToBytes(h string) []byte { + trouble, _ := hex.DecodeString(h) + return trouble + +} + +//-------------------------------------------------------------------------------- +// NOTE: This is all copied from gaia/app/app.go +// so we can access internal fields! + +const ( + appName = "GaiaApp" +) + +// default home directories for expected binaries +var ( + DefaultCLIHome = os.ExpandEnv("$HOME/.gaiacli") + DefaultNodeHome = os.ExpandEnv("$HOME/.gaiad") +) + +// Extended ABCI application +type GaiaApp struct { + *bam.BaseApp + cdc *codec.Codec + + // keys to access the substores + keyMain *sdk.KVStoreKey + keyAccount *sdk.KVStoreKey + keyStaking *sdk.KVStoreKey + tkeyStaking *sdk.TransientStoreKey + keySlashing *sdk.KVStoreKey + keyParams *sdk.KVStoreKey + tkeyParams *sdk.TransientStoreKey + + // Manage getting and setting accounts + accountKeeper auth.AccountKeeper + feeCollectionKeeper auth.FeeCollectionKeeper + bankKeeper bank.Keeper + stakingKeeper staking.Keeper + slashingKeeper slashing.Keeper + paramsKeeper params.Keeper +} + +func NewGaiaApp(logger log.Logger, db dbm.DB, baseAppOptions ...func(*bam.BaseApp)) *GaiaApp { + cdc := MakeCodec() + + bApp := bam.NewBaseApp(appName, logger, db, auth.DefaultTxDecoder(cdc), baseAppOptions...) + bApp.SetCommitMultiStoreTracer(os.Stdout) + + // create your application object + var app = &GaiaApp{ + BaseApp: bApp, + cdc: cdc, + keyMain: sdk.NewKVStoreKey(bam.MainStoreKey), + keyAccount: sdk.NewKVStoreKey(auth.StoreKey), + keyStaking: sdk.NewKVStoreKey(staking.StoreKey), + tkeyStaking: sdk.NewTransientStoreKey(staking.TStoreKey), + keySlashing: sdk.NewKVStoreKey(slashing.StoreKey), + keyParams: sdk.NewKVStoreKey(params.StoreKey), + tkeyParams: sdk.NewTransientStoreKey(params.TStoreKey), + } + + app.paramsKeeper = params.NewKeeper(app.cdc, app.keyParams, app.tkeyParams) + + // define the accountKeeper + app.accountKeeper = auth.NewAccountKeeper( + app.cdc, + app.keyAccount, // target store + app.paramsKeeper.Subspace(auth.DefaultParamspace), + auth.ProtoBaseAccount, // prototype + ) + + // add handlers + app.bankKeeper = bank.NewBaseKeeper(app.accountKeeper, app.paramsKeeper.Subspace(bank.DefaultParamspace), bank.DefaultCodespace) + app.stakingKeeper = staking.NewKeeper(app.cdc, app.keyStaking, app.tkeyStaking, app.bankKeeper, app.paramsKeeper.Subspace(staking.DefaultParamspace), staking.DefaultCodespace) + app.slashingKeeper = slashing.NewKeeper(app.cdc, app.keySlashing, app.stakingKeeper, app.paramsKeeper.Subspace(slashing.DefaultParamspace), slashing.DefaultCodespace) + + // register message routes + app.Router(). + AddRoute("bank", bank.NewHandler(app.bankKeeper)). + AddRoute(staking.RouterKey, staking.NewHandler(app.stakingKeeper)) + + // initialize BaseApp + app.SetInitChainer(app.initChainer) + app.SetBeginBlocker(app.BeginBlocker) + app.SetEndBlocker(app.EndBlocker) + app.SetAnteHandler(auth.NewAnteHandler(app.accountKeeper, app.feeCollectionKeeper)) + app.MountStores(app.keyMain, app.keyAccount, app.keyStaking, app.keySlashing, app.keyParams) + app.MountStore(app.tkeyParams, sdk.StoreTypeTransient) + err := app.LoadLatestVersion(app.keyMain) + if err != nil { + cmn.Exit(err.Error()) + } + + app.Seal() + + return app +} + +// custom tx codec +func MakeCodec() *codec.Codec { + var cdc = codec.New() + bank.RegisterCodec(cdc) + staking.RegisterCodec(cdc) + slashing.RegisterCodec(cdc) + auth.RegisterCodec(cdc) + sdk.RegisterCodec(cdc) + codec.RegisterCrypto(cdc) + cdc.Seal() + return cdc +} + +// application updates every end block +func (app *GaiaApp) BeginBlocker(ctx sdk.Context, req abci.RequestBeginBlock) abci.ResponseBeginBlock { + tags := slashing.BeginBlocker(ctx, req, app.slashingKeeper) + + return abci.ResponseBeginBlock{ + Tags: tags.ToKVPairs(), + } +} + +// application updates every end block +// nolint: unparam +func (app *GaiaApp) EndBlocker(ctx sdk.Context, req abci.RequestEndBlock) abci.ResponseEndBlock { + validatorUpdates, tags := staking.EndBlocker(ctx, app.stakingKeeper) + + return abci.ResponseEndBlock{ + ValidatorUpdates: validatorUpdates, + Tags: tags, + } +} + +// custom logic for gaia initialization +func (app *GaiaApp) initChainer(ctx sdk.Context, req abci.RequestInitChain) abci.ResponseInitChain { + stateJSON := req.AppStateBytes + // TODO is this now the whole genesis file? + + var genesisState gaia.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, "") + } + + // load the accounts + for _, gacc := range genesisState.Accounts { + acc := gacc.ToAccount() + app.accountKeeper.SetAccount(ctx, acc) + } + + // load the initial staking information + validators, err := staking.InitGenesis(ctx, app.stakingKeeper, genesisState.StakingData) + if err != nil { + panic(err) // TODO https://github.com/cosmos/cosmos-sdk/issues/468 // return sdk.ErrGenesisParse("").TraceCause(err, "") + } + + slashing.InitGenesis(ctx, app.slashingKeeper, genesisState.SlashingData, genesisState.StakingData.Validators.ToSDKValidators()) + + return abci.ResponseInitChain{ + Validators: validators, + } +} diff --git a/gaia/cmd/gaiadebug/main.go b/gaia/cmd/gaiadebug/main.go new file mode 100644 index 00000000..095c77a9 --- /dev/null +++ b/gaia/cmd/gaiadebug/main.go @@ -0,0 +1,256 @@ +package main + +import ( + "bytes" + "encoding/base64" + "encoding/hex" + "encoding/json" + "fmt" + "os" + "strconv" + "strings" + + "github.com/spf13/cobra" + "github.com/tendermint/tendermint/crypto" + "github.com/tendermint/tendermint/crypto/ed25519" + + gaia "github.com/cosmos/cosmos-sdk/cmd/gaia/app" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/auth" +) + +func init() { + + config := sdk.GetConfig() + config.SetBech32PrefixForAccount(sdk.Bech32PrefixAccAddr, sdk.Bech32PrefixAccPub) + config.SetBech32PrefixForValidator(sdk.Bech32PrefixValAddr, sdk.Bech32PrefixValPub) + config.SetBech32PrefixForConsensusNode(sdk.Bech32PrefixConsAddr, sdk.Bech32PrefixConsPub) + config.Seal() + + rootCmd.AddCommand(txCmd) + rootCmd.AddCommand(pubkeyCmd) + rootCmd.AddCommand(addrCmd) + rootCmd.AddCommand(hackCmd) + rootCmd.AddCommand(rawBytesCmd) +} + +var rootCmd = &cobra.Command{ + Use: "gaiadebug", + Short: "Gaia debug tool", + SilenceUsage: true, +} + +var txCmd = &cobra.Command{ + Use: "tx", + Short: "Decode a gaia tx from hex or base64", + RunE: runTxCmd, +} + +var pubkeyCmd = &cobra.Command{ + Use: "pubkey", + Short: "Decode a pubkey from hex, base64, or bech32", + RunE: runPubKeyCmd, +} + +var addrCmd = &cobra.Command{ + Use: "addr", + Short: "Convert an address between hex and bech32", + RunE: runAddrCmd, +} + +var hackCmd = &cobra.Command{ + Use: "hack", + Short: "Boilerplate to Hack on an existing state by scripting some Go...", + RunE: runHackCmd, +} + +var rawBytesCmd = &cobra.Command{ + Use: "raw-bytes", + Short: "Convert raw bytes output (eg. [10 21 13 255]) to hex", + RunE: runRawBytesCmd, +} + +func runRawBytesCmd(cmd *cobra.Command, args []string) error { + if len(args) != 1 { + return fmt.Errorf("Expected single arg") + } + stringBytes := args[0] + stringBytes = strings.Trim(stringBytes, "[") + stringBytes = strings.Trim(stringBytes, "]") + spl := strings.Split(stringBytes, " ") + + byteArray := []byte{} + for _, s := range spl { + b, err := strconv.Atoi(s) + if err != nil { + return err + } + byteArray = append(byteArray, byte(b)) + } + fmt.Printf("%X\n", byteArray) + return nil +} + +func runPubKeyCmd(cmd *cobra.Command, args []string) error { + if len(args) != 1 { + return fmt.Errorf("Expected single arg") + } + + pubkeyString := args[0] + var pubKeyI crypto.PubKey + + // try hex, then base64, then bech32 + pubkeyBytes, err := hex.DecodeString(pubkeyString) + if err != nil { + var err2 error + pubkeyBytes, err2 = base64.StdEncoding.DecodeString(pubkeyString) + if err2 != nil { + var err3 error + pubKeyI, err3 = sdk.GetAccPubKeyBech32(pubkeyString) + if err3 != nil { + var err4 error + pubKeyI, err4 = sdk.GetValPubKeyBech32(pubkeyString) + + if err4 != nil { + var err5 error + pubKeyI, err5 = sdk.GetConsPubKeyBech32(pubkeyString) + if err5 != nil { + return fmt.Errorf(`Expected hex, base64, or bech32. Got errors: + hex: %v, + base64: %v + bech32 Acc: %v + bech32 Val: %v + bech32 Cons: %v`, + err, err2, err3, err4, err5) + } + + } + } + + } + } + + var pubKey ed25519.PubKeyEd25519 + if pubKeyI == nil { + copy(pubKey[:], pubkeyBytes) + } else { + pubKey = pubKeyI.(ed25519.PubKeyEd25519) + pubkeyBytes = pubKey[:] + } + + cdc := gaia.MakeCodec() + pubKeyJSONBytes, err := cdc.MarshalJSON(pubKey) + if err != nil { + return err + } + accPub, err := sdk.Bech32ifyAccPub(pubKey) + if err != nil { + return err + } + valPub, err := sdk.Bech32ifyValPub(pubKey) + if err != nil { + return err + } + + consenusPub, err := sdk.Bech32ifyConsPub(pubKey) + if err != nil { + return err + } + fmt.Println("Address:", pubKey.Address()) + fmt.Printf("Hex: %X\n", pubkeyBytes) + fmt.Println("JSON (base64):", string(pubKeyJSONBytes)) + fmt.Println("Bech32 Acc:", accPub) + fmt.Println("Bech32 Validator Operator:", valPub) + fmt.Println("Bech32 Validator Consensus:", consenusPub) + return nil +} + +func runAddrCmd(cmd *cobra.Command, args []string) error { + if len(args) != 1 { + return fmt.Errorf("Expected single arg") + } + + addrString := args[0] + var addr []byte + + // try hex, then bech32 + var err error + addr, err = hex.DecodeString(addrString) + if err != nil { + var err2 error + addr, err2 = sdk.AccAddressFromBech32(addrString) + if err2 != nil { + var err3 error + addr, err3 = sdk.ValAddressFromBech32(addrString) + + if err3 != nil { + return fmt.Errorf(`Expected hex or bech32. Got errors: + hex: %v, + bech32 acc: %v + bech32 val: %v + `, err, err2, err3) + + } + } + } + + accAddr := sdk.AccAddress(addr) + valAddr := sdk.ValAddress(addr) + + fmt.Println("Address:", addr) + fmt.Printf("Address (hex): %X\n", addr) + fmt.Printf("Bech32 Acc: %s\n", accAddr) + fmt.Printf("Bech32 Val: %s\n", valAddr) + return nil +} + +func runTxCmd(cmd *cobra.Command, args []string) error { + if len(args) != 1 { + return fmt.Errorf("Expected single arg") + } + + txString := args[0] + + // try hex, then base64 + txBytes, err := hex.DecodeString(txString) + if err != nil { + var err2 error + txBytes, err2 = base64.StdEncoding.DecodeString(txString) + if err2 != nil { + return fmt.Errorf(`Expected hex or base64. Got errors: + hex: %v, + base64: %v + `, err, err2) + } + } + + var tx = auth.StdTx{} + cdc := gaia.MakeCodec() + + err = cdc.UnmarshalBinaryLengthPrefixed(txBytes, &tx) + if err != nil { + return err + } + + bz, err := cdc.MarshalJSON(tx) + if err != nil { + return err + } + + buf := bytes.NewBuffer([]byte{}) + err = json.Indent(buf, bz, "", " ") + if err != nil { + return err + } + + fmt.Println(buf.String()) + return nil +} + +func main() { + err := rootCmd.Execute() + if err != nil { + os.Exit(1) + } + os.Exit(0) +} diff --git a/gaia/cmd/gaiakeyutil/main.go b/gaia/cmd/gaiakeyutil/main.go new file mode 100644 index 00000000..46eefc69 --- /dev/null +++ b/gaia/cmd/gaiakeyutil/main.go @@ -0,0 +1,59 @@ +package main + +import ( + "encoding/hex" + "fmt" + "os" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/tendermint/tendermint/libs/bech32" +) + +var bech32Prefixes = []string{ + sdk.Bech32PrefixAccAddr, + sdk.Bech32PrefixAccPub, + sdk.Bech32PrefixValAddr, + sdk.Bech32PrefixValPub, + sdk.Bech32PrefixConsAddr, + sdk.Bech32PrefixConsPub, +} + +func main() { + if len(os.Args) < 2 { + fmt.Println("Must specify an input string") + } + arg := os.Args[1] + runFromBech32(arg) + runFromHex(arg) +} + +// Print info from bech32. +func runFromBech32(bech32str string) { + hrp, bz, err := bech32.DecodeAndConvert(bech32str) + if err != nil { + fmt.Println("Not a valid bech32 string") + return + } + fmt.Println("Bech32 parse:") + fmt.Printf("Human readible part: %v\nBytes (hex): %X\n", + hrp, + bz, + ) +} + +func runFromHex(hexaddr string) { + bz, err := hex.DecodeString(hexaddr) + if err != nil { + fmt.Println("Not a valid hex string") + return + } + fmt.Println("Hex parse:") + fmt.Println("Bech32 formats:") + for _, prefix := range bech32Prefixes { + bech32Addr, err := bech32.ConvertAndEncode(prefix, bz) + if err != nil { + panic(err) + } + fmt.Println(" - " + bech32Addr) + } +} diff --git a/cmd/kvd/replay.go b/gaia/cmd/gaiareplay/main.go similarity index 74% rename from cmd/kvd/replay.go rename to gaia/cmd/gaiareplay/main.go index 12d2e0ad..d41a65c7 100644 --- a/cmd/kvd/replay.go +++ b/gaia/cmd/gaiareplay/main.go @@ -1,6 +1,3 @@ -// Copyright 2016 All in Bits, inc -// Modifications copyright 2019 Kava Labs - package main import ( @@ -10,13 +7,11 @@ import ( "path/filepath" "time" + "github.com/cosmos/cosmos-sdk/store" + 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" @@ -24,32 +19,49 @@ import ( tmsm "github.com/tendermint/tendermint/state" tm "github.com/tendermint/tendermint/types" - "github.com/kava-labs/_/app" + "github.com/cosmos/cosmos-sdk/baseapp" + "github.com/cosmos/cosmos-sdk/cmd/gaia/app" + "github.com/cosmos/cosmos-sdk/server" + sdk "github.com/cosmos/cosmos-sdk/types" ) -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), +var ( + rootDir string +) + +var rootCmd = &cobra.Command{ + Use: "gaiareplay", + Short: "Replay gaia transactions", + Run: func(cmd *cobra.Command, args []string) { + run(rootDir) + }, +} + +func init() { + // cobra.OnInitialize(initConfig) + rootCmd.PersistentFlags().StringVar(&rootDir, "root", "r", "root dir") +} + +func main() { + if err := rootCmd.Execute(); err != nil { + fmt.Println(err) + os.Exit(1) } } -func replayTxs(rootDir string) error { +func run(rootDir string) { if false { // Copy the rootDir to a new directory, to preserve the old one. - fmt.Fprintln(os.Stderr, "Copying rootdir over") + fmt.Println("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 + err := cpm.Copy(oldRootDir, rootDir) + if err != nil { + panic(err) } } @@ -59,25 +71,25 @@ func replayTxs(rootDir string) error { // App DB // appDB := dbm.NewMemDB() - fmt.Fprintln(os.Stderr, "Opening app database") + fmt.Println("Opening app database") appDB, err := sdk.NewLevelDB("application", dataDir) if err != nil { - return err + panic(err) } // TM DB // tmDB := dbm.NewMemDB() - fmt.Fprintln(os.Stderr, "Opening tendermint state database") + fmt.Println("Opening tendermint state database") tmDB, err := sdk.NewLevelDB("state", dataDir) if err != nil { - return err + panic(err) } // Blockchain DB - fmt.Fprintln(os.Stderr, "Opening blockstore database") + fmt.Println("Opening blockstore database") bcDB, err := sdk.NewLevelDB("blockstore", dataDir) if err != nil { - return err + panic(err) } // TraceStore @@ -89,12 +101,12 @@ func replayTxs(rootDir string) error { 0666, ) if err != nil { - return err + panic(err) } // Application - fmt.Fprintln(os.Stderr, "Creating application") - myapp := app.NewApp( + fmt.Println("Creating application") + myapp := app.NewGaiaApp( ctx.Logger, appDB, traceStoreWriter, true, uint(1), baseapp.SetPruning(store.PruneEverything), // nothing ) @@ -103,11 +115,11 @@ func replayTxs(rootDir string) error { var genDocPath = filepath.Join(configDir, "genesis.json") genDoc, err := tm.GenesisDocFromFile(genDocPath) if err != nil { - return err + panic(err) } genState, err := tmsm.MakeGenesisState(genDoc) if err != nil { - return err + panic(err) } // tmsm.SaveState(tmDB, genState) @@ -115,7 +127,7 @@ func replayTxs(rootDir string) error { proxyApp := proxy.NewAppConns(cc) err = proxyApp.Start() if err != nil { - return err + panic(err) } defer func() { _ = proxyApp.Stop() @@ -124,7 +136,7 @@ func replayTxs(rootDir string) error { state := tmsm.LoadState(tmDB) if state.LastBlockHeight == 0 { // Send InitChain msg - fmt.Fprintln(os.Stderr, "Sending InitChain msg") + fmt.Println("Sending InitChain msg") validators := tm.TM2PB.ValidatorUpdates(genState.Validators) csParams := tm.TM2PB.ConsensusParams(genDoc.ConsensusParams) req := abci.RequestInitChain{ @@ -136,11 +148,11 @@ func replayTxs(rootDir string) error { } res, err := proxyApp.Consensus().InitChainSync(req) if err != nil { - return err + panic(err) } newValidatorz, err := tm.PB2TM.ValidatorUpdates(res.Validators) if err != nil { - return err + panic(err) } newValidators := tm.NewValidatorSet(newValidatorz) @@ -151,17 +163,17 @@ func replayTxs(rootDir string) error { } // Create executor - fmt.Fprintln(os.Stderr, "Creating block executor") + fmt.Println("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") + fmt.Println("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) + fmt.Println("Running block ", i) t1 := time.Now() // Apply block @@ -169,25 +181,26 @@ func replayTxs(rootDir string) error { blockmeta := blockStore.LoadBlockMeta(int64(i)) if blockmeta == nil { fmt.Printf("Couldn't find block meta %d... done?\n", i) - return nil + return } block := blockStore.LoadBlock(int64(i)) if block == nil { - return fmt.Errorf("couldn't find block %d", i) + panic(fmt.Sprintf("couldn't find block %d", i)) } t2 := time.Now() state, err = blockExec.ApplyBlock(state, blockmeta.BlockID, block) if err != nil { - return err + panic(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) + fmt.Printf("new app hash: %X\n", state.AppHash) + fmt.Println(tz) } + } diff --git a/gaia/init/collect.go b/gaia/init/collect.go new file mode 100644 index 00000000..85863948 --- /dev/null +++ b/gaia/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/cosmos/cosmos-sdk/cmd/gaia/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.GaiaAppGenStateJSON(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/gaia/init/genesis_accts.go b/gaia/init/genesis_accts.go new file mode 100644 index 00000000..5aa73dae --- /dev/null +++ b/gaia/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/cmd/gaia/app" + "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" +) + +// 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 `gaiad 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/gaia/init/genesis_accts_test.go b/gaia/init/genesis_accts_test.go new file mode 100644 index 00000000..74af5a42 --- /dev/null +++ b/gaia/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/cosmos/cosmos-sdk/cmd/gaia/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/gaia/init/gentx.go b/gaia/init/gentx.go new file mode 100644 index 00000000..970c77e0 --- /dev/null +++ b/gaia/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/cmd/gaia/app" + "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" +) + +var ( + defaultTokens = sdk.TokensFromTendermintPower(100) + defaultAmount = defaultTokens.String() + sdk.DefaultBondDenom + defaultCommissionRate = "0.1" + defaultCommissionMaxRate = "0.2" + defaultCommissionMaxChangeRate = "0.01" + defaultMinSelfDelegation = "1" +) + +// GenTxCmd builds the gaiad 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 'gaiad 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.GaiaValidateGenesisState(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 `gaiacli 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/gaia/init/gentx_test.go b/gaia/init/gentx_test.go new file mode 100644 index 00000000..d99a562f --- /dev/null +++ b/gaia/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/gaia/init/init.go b/gaia/init/init.go new file mode 100644 index 00000000..506557d3 --- /dev/null +++ b/gaia/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/cosmos/cosmos-sdk/cmd/gaia/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/gaia/init/init_test.go b/gaia/init/init_test.go new file mode 100644 index 00000000..39975bb2 --- /dev/null +++ b/gaia/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/cosmos/cosmos-sdk/cmd/gaia/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{"gaianode-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{"gaianode-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{"gaianode-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/gaia/init/testnet.go similarity index 59% rename from cmd/kvd/testnet.go rename to gaia/init/testnet.go index 4c2e256b..fc59827a 100644 --- a/cmd/kvd/testnet.go +++ b/gaia/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/cmd/gaia/app" + "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/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,70 +36,68 @@ 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 Gaiad testnet", Long: `testnet will create "v" number of directories and populate each with necessary files (private validator, genesis, config, etc.). Note, strict routability for addresses is turned off in the config file. Example: - kvd testnet --v 4 --output-dir ./output --starting-ip-address 192.168.10.2 + gaiad testnet --v 4 --output-dir ./output --starting-ip-address 192.168.10.2 `, 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, ...)") - 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") + "Prefix the directory name for each node with (node results in node0, node1, ...)", + ) + cmd.Flags().String(flagNodeDaemonHome, "gaiad", + "Home directory of the node's daemon configuration", + ) + cmd.Flags().String(flagNodeCliHome, "gaiacli", + "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) } @@ -110,47 +106,49 @@ func InitTestnet(config *tmconfig.Config, cdc *codec.Codec, mbm module.BasicMana nodeIDs := make([]string, numValidators) valPubKeys := make([]crypto.PubKey, numValidators) - appConfig := srvconfig.DefaultConfig() - appConfig.MinGasPrices = minGasPrices + gaiaConfig := srvconfig.DefaultConfig() + gaiaConfig.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") - srvconfig.WriteConfigFile(appConfigFilePath, appConfig) + gaiaConfigFilePath := filepath.Join(nodeDir, "config/gaiad.toml") + srvconfig.WriteConfigFile(gaiaConfigFilePath, gaiaConfig) } - 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/gaia/init/utils.go b/gaia/init/utils.go new file mode 100644 index 00000000..4e2f2f26 --- /dev/null +++ b/gaia/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/cosmos/cosmos-sdk/cmd/gaia/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/gaia/init/utils_test.go b/gaia/init/utils_test.go new file mode 100644 index 00000000..3a5a1369 --- /dev/null +++ b/gaia/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/gaia/init/validate_genesis.go b/gaia/init/validate_genesis.go new file mode 100644 index 00000000..eb0eaa3a --- /dev/null +++ b/gaia/init/validate_genesis.go @@ -0,0 +1,51 @@ +package init + +import ( + "fmt" + "os" + + "github.com/cosmos/cosmos-sdk/cmd/gaia/app" + "github.com/cosmos/cosmos-sdk/codec" + "github.com/cosmos/cosmos-sdk/server" + "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.GaiaValidateGenesisState(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 gaiad\n", genesis) + return nil + }, + } +} diff --git a/gaia/testnets/README.md b/gaia/testnets/README.md new file mode 100644 index 00000000..e64db9f7 --- /dev/null +++ b/gaia/testnets/README.md @@ -0,0 +1,7 @@ +# DEPRECATED + +The content of this file was moved to the `/docs` folder and is hosted on the +[website](https://cosmos.network/docs/getting-started/full-node.html#run-a-full-node). + +The rest of this folder was moved to the [testnets +repo](https://github.com/cosmos/testnets). diff --git a/gaia/testnets/STATUS.md b/gaia/testnets/STATUS.md new file mode 100644 index 00000000..4db42978 --- /dev/null +++ b/gaia/testnets/STATUS.md @@ -0,0 +1,132 @@ +# DEPRECATED + +See [testnets repo](https://github.com/cosmos/testnets). + +## *July 22, 2018, 5:30 EST* - Gaia-7001 Consensus Failure + +- [Consensus Failure at Block 24570](https://github.com/cosmos/cosmos-sdk/issues/1787) + + +## *July 17, 2018, 4:00 EST* - New Testnet Gaia-7001 + +- New testnet with fixes for the genesis file +- Increased max validators to 128 + +## *July 17, 2018, 3:00 EST* - Gaia-7000 consensus failure + +- Misconfiguration in the genesis file led to a consensus failure +- New genesis file for gaia-7001 will be up soon + +## *July 17, 2018, 2:40 EST* - Gaia-7000 is making blocks! + +- Gaia-7000 is live and making blocks! + +## *July 16, 2018, 17:00 EST* - New Testnet Gaia-7000 + +- Gaia-7000 is up! +- 108 validators in the genesis.json file. + +## *July 2, 2018, 1:00 EST* - Gaia-6002 slashing failure + +- Gaia-6002 has been halted due to a slashing issue. +- The team is taking its time to look into this Gaia-7000 will be introduced this week. + +## *June 13, 2018, 17:00 EST* - Gaia-6002 is making blocks! + +- Gaia-6002 is live and making blocks +- Absent validators have been slashed and jailed +- Currently live with 17 validators + +## *June 13, 2018, 4:30 EST* - New Testnet Gaia-6002 + +- After fixing bugs from gaia-6001, especially [issue + #1197](https://github.com/cosmos/cosmos-sdk/issues/1197), we are announcing a + new testnet, Gaia-6002 +- Gaia-6002 has the same genesis file as Gaia-6001, just with the chain-id + updated +- Update from previous testnet [here](https://github.com/cosmos/cosmos-sdk/tree/master/cmd/gaia/testnets#upgrading-from-previous-testnet) + +## *June 13, 2018, 4:30 EST* - New Release + +- Released gaia + [v0.19.0](https://github.com/cosmos/cosmos-sdk/releases/tag/v0.19.0) +- Includes various bug-fixes for staking found on Gaia-6001 + +## *June 13, 2018, 2:30 EST* - Published Postmortem of Gaia-6001 failure + +- A bug in the design of the staking data model caused a sanity check to fail +- Full writeup + [here](https://github.com/cosmos/cosmos-sdk/issues/1197#issuecomment-396823021) + +## *June 10, 2018, 8:30 EST* - Gaia-6001 consensus failure + +- Validator unbonding and revocation activity caused a consensus failure +- There is a bug in the staking module that must be fixed +- The team is taking its time to look into this and release a fix following a + proper protocol for hotfix upgrades to the testnet +- Please stay tuned! + +## *June 9, 2018, 14:00 EST* - New Release + +- Released gaia + [v0.18.0](https://github.com/cosmos/cosmos-sdk/releases/tag/v0.18.0) with + update for Tendermint + [v0.20.0](https://github.com/tendermint/tendermint/releases/tag/v0.20.0) +- Includes bug fix for declaring candidacy from the command line + +## *June 8, 2018, 23:30 EST* - Gaia-6001 is making blocks + +- +2/3 of the voting power is finally online for Gaia-6001 and it is making + blocks! +- This is a momentous achievement - a successful asynchronous decentralized + testnet launch +- Congrats everyone! + +## *June 8, 2018, 12:00 EST* - New Testnet Gaia-6001 + +- After some confusion around testnet deployment and a contention testnet + hardfork, a new genesis file and network was released for `gaia-6001` + +## *June 7, 2018, 9:00 EST* - New Testnet Gaia-6000 + +- Released a new `genesis.json` file for `gaia-6000` +- Initial validators include those that were most active in + the gaia-5001 testnet +- Join the network via gaia `v0.18.0-rc0` + +## *June 5, 2018, 21:00 EST* - New Release + +- Released gaia + [v0.17.5](https://github.com/cosmos/cosmos-sdk/releases/tag/v0.17.5) + with update for Tendermint + [v0.19.9](https://github.com/tendermint/tendermint/releases/tag/v0.19.9) +- Fixes many bugs! + - evidence gossipping + - mempool deadlock + - WAL panic + - memory leak +- Please update to this to put a stop to the rampant invalid evidence gossiping + :) + +## *May 31, 2018, 14:00 EST* - New Release + +- Released gaia + [v0.17.4](https://github.com/cosmos/cosmos-sdk/releases/tag/v0.17.4) with update for Tendermint v0.19.7 +- Fixes a WAL bug and some more +- Please update to this if you have trouble restarting a node + +## *May 31, 2018, 2:00 EST* - Testnet Halt + +- A validator equivocated last week and Evidence is being rampantly gossipped +- Peers that can't process the evidence (either too far behind or too far ahead) are disconnecting from the peers that + sent it, causing high peer turn-over +- The high peer turn-over may be causing a memory-leak, resulting in some nodes + crashing and the testnet halting +- We need to fix some issues in the EvidenceReactor to address this and also + investigate the possible memory-leak + +## *May 29, 2018* - New Release + +- Released v0.17.3 with update for Tendermint v0.19.6 +- Fixes fast-sync bug +- Please update to this to sync with the testnet diff --git a/go.mod b/go.mod index 679d6845..ef57b493 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..11c135e5 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,15 +60,15 @@ 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= @@ -75,8 +77,8 @@ github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= 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 +102,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 +120,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 +135,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 +168,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 +215,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 +226,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 +244,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 +258,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=