Now that your module is ready, it can be incorporated in the ./app.go
file, along with the other two modules auth
and bank
. Let's begin by adding your new nameservice module to the imports:
NOTE: Your application needs to import the code you just wrote. Here the import path is set to this repository (
github.com/cosmos/sdk-application-tutorial/x/nameservice
). If you are following along in your own repo you will need to change the import path to reflect that (github.com/{ .Username }/{ .Project.Repo }/x/nameservice
).
package app
import (
"encoding/json"
"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"
tmtypes "github.com/tendermint/tendermint/types"
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/x/auth"
"github.com/cosmos/cosmos-sdk/x/bank"
distr "github.com/cosmos/cosmos-sdk/x/distribution"
"github.com/cosmos/cosmos-sdk/x/genaccounts"
"github.com/cosmos/cosmos-sdk/x/genutil"
"github.com/cosmos/cosmos-sdk/x/params"
"github.com/cosmos/cosmos-sdk/x/slashing"
"github.com/cosmos/cosmos-sdk/x/staking"
"github.com/cosmos/cosmos-sdk/x/supply"
"github.com/cosmos/sdk-application-tutorial/x/nameservice"
)
const appName = "nameservice"
var (
// default home directories for the application CLI
DefaultCLIHome = os.ExpandEnv("$HOME/.nscli")
// DefaultNodeHome sets the folder where the applcation data and configuration will be stored
DefaultNodeHome = os.ExpandEnv("$HOME/.nsd")
// NewBasicManager is in charge of setting up basic module elemnets
ModuleBasics = module.NewBasicManager(
genaccounts.AppModuleBasic{},
genutil.AppModuleBasic{},
auth.AppModuleBasic{},
bank.AppModuleBasic{},
staking.AppModuleBasic{},
distr.AppModuleBasic{},
params.AppModuleBasic{},
slashing.AppModuleBasic{},
supply.AppModuleBasic{},
nameservice.AppModule{},
)
// account permissions
maccPerms = map[string][]string{
auth.FeeCollectorName: nil,
distr.ModuleName: nil,
staking.BondedPoolName: []string{supply.Burner, supply.Staking},
staking.NotBondedPoolName: []string{supply.Burner, supply.Staking},
}
)
Next you need to add the stores' keys and the Keepers
into your nameServiceApp
struct.
type nameServiceApp struct {
*bam.BaseApp
cdc *codec.Codec
// Keys to access the substores
keyMain *sdk.KVStoreKey
keyAccount *sdk.KVStoreKey
keySupply *sdk.KVStoreKey
keyStaking *sdk.KVStoreKey
tkeyStaking *sdk.TransientStoreKey
keyDistr *sdk.KVStoreKey
tkeyDistr *sdk.TransientStoreKey
keyNS *sdk.KVStoreKey
keyParams *sdk.KVStoreKey
tkeyParams *sdk.TransientStoreKey
keySlashing *sdk.KVStoreKey
// Keepers
accountKeeper auth.AccountKeeper
bankKeeper bank.Keeper
stakingKeeper staking.Keeper
slashingKeeper slashing.Keeper
distrKeeper distr.Keeper
supplyKeeper supply.Keeper
paramsKeeper params.Keeper
nsKeeper nameservice.Keeper
// Module Manager
mm *module.Manager
}
// NewNameServiceApp is a constructor function for nameServiceApp
func NewNameServiceApp(logger log.Logger, db dbm.DB) *nameServiceApp {
// First define the top level codec that will be shared by the different modules
cdc := MakeCodec()
// BaseApp handles interactions with Tendermint through the ABCI protocol
bApp := bam.NewBaseApp(appName, logger, db, auth.DefaultTxDecoder(cdc))
// Here you initialize your application with the store keys it requires
var app = &nameServiceApp{
BaseApp: bApp,
cdc: cdc,
keyMain: sdk.NewKVStoreKey(bam.MainStoreKey),
keyAccount: sdk.NewKVStoreKey(auth.StoreKey),
keySupply: sdk.NewKVStoreKey(supply.StoreKey),
keyStaking: sdk.NewKVStoreKey(staking.StoreKey),
tkeyStaking: sdk.NewTransientStoreKey(staking.TStoreKey),
keyDistr: sdk.NewKVStoreKey(distr.StoreKey),
tkeyDistr: sdk.NewTransientStoreKey(distr.TStoreKey),
keyNS: sdk.NewKVStoreKey(nameservice.StoreKey),
keyParams: sdk.NewKVStoreKey(params.StoreKey),
tkeyParams: sdk.NewTransientStoreKey(params.TStoreKey),
keySlashing: sdk.NewKVStoreKey(slashing.StoreKey),
}
}
At this point, the constructor still lacks important logic. Namely, it needs to:
- Instantiate required
Keepers
from each desired module. - Generate
storeKeys
required by eachKeeper
. - Register
Handler
s from each module. TheAddRoute()
method frombaseapp
'srouter
is used to this end. - Register
Querier
s from each module. TheAddRoute()
method frombaseapp
'squeryRouter
is used to this end. - Mount
KVStore
s to the provided keys in thebaseApp
multistore. - Set the
initChainer
for defining the initial application state.
Your finalized constructor should look like this:
// NewNameServiceApp is a constructor function for nameServiceApp
func NewNameServiceApp(logger log.Logger, db dbm.DB) *nameServiceApp {
// First define the top level codec that will be shared by the different modules
cdc := MakeCodec()
// BaseApp handles interactions with Tendermint through the ABCI protocol
bApp := bam.NewBaseApp(appName, logger, db, auth.DefaultTxDecoder(cdc))
// Here you initialize your application with the store keys it requires
var app = &nameServiceApp{
BaseApp: bApp,
cdc: cdc,
keyMain: sdk.NewKVStoreKey(bam.MainStoreKey),
keyAccount: sdk.NewKVStoreKey(auth.StoreKey),
keySupply: sdk.NewKVStoreKey(supply.StoreKey),
keyStaking: sdk.NewKVStoreKey(staking.StoreKey),
tkeyStaking: sdk.NewTransientStoreKey(staking.TStoreKey),
keyDistr: sdk.NewKVStoreKey(distr.StoreKey),
tkeyDistr: sdk.NewTransientStoreKey(distr.TStoreKey),
keyNS: sdk.NewKVStoreKey(nameservice.StoreKey),
keyParams: sdk.NewKVStoreKey(params.StoreKey),
tkeyParams: sdk.NewTransientStoreKey(params.TStoreKey),
keySlashing: sdk.NewKVStoreKey(slashing.StoreKey),
}
// The ParamsKeeper handles parameter storage for the application
app.paramsKeeper = params.NewKeeper(app.cdc, app.keyParams, app.tkeyParams, params.DefaultCodespace)
// Set specific supspaces
authSubspace := app.paramsKeeper.Subspace(auth.DefaultParamspace)
bankSupspace := app.paramsKeeper.Subspace(bank.DefaultParamspace)
stakingSubspace := app.paramsKeeper.Subspace(staking.DefaultParamspace)
distrSubspace := app.paramsKeeper.Subspace(distr.DefaultParamspace)
slashingSubspace := app.paramsKeeper.Subspace(slashing.DefaultParamspace)
// The AccountKeeper handles address -> account lookups
app.accountKeeper = auth.NewAccountKeeper(
app.cdc,
app.keyAccount,
authSubspace,
auth.ProtoBaseAccount,
)
// The BankKeeper allows you perform sdk.Coins interactions
app.bankKeeper = bank.NewBaseKeeper(
app.accountKeeper,
bankSupspace,
bank.DefaultCodespace,
)
// The SupplyKeeper collects transaction fees and renders them to the fee distribution module
app.supplyKeeper = supply.NewKeeper(
app.cdc,
app.keySupply,
app.accountKeeper,
app.bankKeeper,
supply.DefaultCodespace,
maccPerms)
// The staking keeper
stakingKeeper := staking.NewKeeper(
app.cdc,
app.keyStaking,
app.tkeyStaking,
app.supplyKeeper,
stakingSubspace,
staking.DefaultCodespace,
)
app.distrKeeper = distr.NewKeeper(
app.cdc,
app.keyDistr,
distrSubspace,
&stakingKeeper,
app.supplyKeeper,
distr.DefaultCodespace,
auth.FeeCollectorName,
)
app.slashingKeeper = slashing.NewKeeper(
app.cdc,
app.keySlashing,
&stakingKeeper,
slashingSubspace,
slashing.DefaultCodespace,
)
// 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()),
)
// The NameserviceKeeper is the Keeper from the module for this tutorial
// It handles interactions with the namestore
app.nsKeeper = nameservice.NewKeeper(
app.bankKeeper,
app.keyNS,
app.cdc,
)
app.mm = module.NewManager(
genaccounts.NewAppModule(app.accountKeeper),
genutil.NewAppModule(app.accountKeeper, app.stakingKeeper, app.BaseApp.DeliverTx),
auth.NewAppModule(app.accountKeeper),
bank.NewAppModule(app.bankKeeper, app.accountKeeper),
nameservice.NewAppModule(app.nsKeeper, app.bankKeeper),
supply.NewAppModule(app.supplyKeeper, app.accountKeeper),
distr.NewAppModule(app.distrKeeper, app.supplyKeeper),
slashing.NewAppModule(app.slashingKeeper, app.stakingKeeper),
staking.NewAppModule(app.stakingKeeper, app.distrKeeper, app.accountKeeper, app.supplyKeeper),
)
app.mm.SetOrderBeginBlockers(distr.ModuleName, slashing.ModuleName)
app.mm.SetOrderEndBlockers(staking.ModuleName)
// Sets the order of Genesis - Order matters, genutil is to always come last
app.mm.SetOrderInitGenesis(
genaccounts.ModuleName,
distr.ModuleName,
staking.ModuleName,
auth.ModuleName,
bank.ModuleName,
slashing.ModuleName,
nameservice.ModuleName,
genutil.ModuleName,
)
// register all module routes and module queriers
app.mm.RegisterRoutes(app.Router(), app.QueryRouter())
// The initChainer handles translating the genesis.json file into initial state for the network
app.SetInitChainer(app.InitChainer)
app.SetBeginBlocker(app.BeginBlocker)
app.SetEndBlocker(app.EndBlocker)
// The AnteHandler handles signature verification and transaction pre-processing
app.SetAnteHandler(
auth.NewAnteHandler(
app.accountKeeper,
app.supplyKeeper,
auth.DefaultSigVerificationGasConsumer,
),
)
app.MountStores(
app.keyMain,
app.keyAccount,
app.keySupply,
app.keyStaking,
app.tkeyStaking,
app.keyDistr,
app.tkeyDistr,
app.keySlashing,
app.keyNS,
app.keyParams,
app.tkeyParams,
)
err := app.LoadLatestVersion(app.keyMain)
if err != nil {
cmn.Exit(err.Error())
}
return app
}
NOTE: The TransientStore mentioned above is an in-memory implementation of the KVStore for state that is not persisted.
NOTE: Pay attention to how the modules are initiated: the order matters! Here the sequence goes Auth --> Bank --> Feecollection --> Staking --> Distribution --> Slashing, then the hooks were set for the staking module. This is because some of these modules depend on others existing before they can be used.
The initChainer
defines how accounts in genesis.json
are mapped into the application state on initial chain start. The ExportAppStateAndValidators
function helps bootstrap the initial state for the application. You don't need to worry too much about either of these for now. We also need to add a few more methods to our app BeginBlocker
, EndBlocker
and LoadHeight
.
The constructor registers the initChainer
function, but it isn't defined yet. Go ahead and create it:
// GenesisState represents chain state at the start of the chain. Any initial state (account balances) are stored here.
type GenesisState map[string]json.RawMessage
func NewDefaultGenesisState() GenesisState {
return ModuleBasics.DefaultGenesis()
}
func (app *nameServiceApp) InitChainer(ctx sdk.Context, req abci.RequestInitChain) abci.ResponseInitChain {
var genesisState GenesisState
err := app.cdc.UnmarshalJSON(req.AppStateBytes, &genesisState)
if err != nil {
panic(err)
}
return app.mm.InitGenesis(ctx, genesisState)
}
func (app *nameServiceApp) BeginBlocker(ctx sdk.Context, req abci.RequestBeginBlock) abci.ResponseBeginBlock {
return app.mm.BeginBlock(ctx, req)
}
func (app *nameServiceApp) EndBlocker(ctx sdk.Context, req abci.RequestEndBlock) abci.ResponseEndBlock {
return app.mm.EndBlock(ctx, req)
}
func (app *nameServiceApp) LoadHeight(height int64) error {
return app.LoadVersion(height, app.keyMain)
}
//_________________________________________________________
func (app *nameServiceApp) 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()})
genState := app.mm.ExportGenesis(ctx)
appState, err = codec.MarshalJSONIndent(app.cdc, genState)
if err != nil {
return nil, nil, err
}
validators = staking.WriteValidators(ctx, app.stakingKeeper)
return appState, validators, nil
}
Finally add a helper function to generate an amino *codec.Codec
that properly registers all of the modules used in your application:
// MakeCodec generates the necessary codecs for Amino
func MakeCodec() *codec.Codec {
var cdc = codec.New()
ModuleBasics.RegisterCodec(cdc)
sdk.RegisterCodec(cdc)
codec.RegisterCrypto(cdc)
return cdc
}