The main core of a Cosmos SDK module is a piece called the Keeper. It is what handles interaction with the store (the abstraction over tendermint
), has references to other keepers for cross-module interactions, and contains most of the core functionality of a module.
Begin by creating the file ./x/nameservice/keeper.go
to hold the keeper for your module. In Cosmos SDK applications the convention is that modules live in the ./x/
folder.
To start your SDK module, add the following code to ./x/nameservice/keeper.go
:
package nameservice
import (
"github.com/cosmos/cosmos-sdk/codec"
"github.com/cosmos/cosmos-sdk/x/bank"
sdk "github.com/cosmos/cosmos-sdk/types"
)
// Keeper maintains the link to data storage and exposes getter/setter methods for the various parts of the state machine
type Keeper struct {
coinKeeper bank.Keeper
namesStoreKey sdk.StoreKey // Unexposed key to access name store from sdk.Context
ownersStoreKey sdk.StoreKey // Unexposed key to access owners store from sdk.Context
pricesStoreKey sdk.StoreKey // Unexposed key to access prices store from sdk.Context
cdc *codec.Codec // The wire codec for binary encoding/decoding.
}
A couple of notes about the above code:
- Note the
import
of 3 differentcosmos-sdk
packages: - The
Keeper
struct. In this keeper there are a couple of key pieces:bank.Keeper
- This is a reference to the Keeper from the bank module. Including it allows code in this module to call functions from the bank module. The SDK uses an object capabilities approach to accessing parts of the sections of the application state. This is to allow developers to employ a least authority approach limiting the capabilities of a faulty or malicious module from affecting parts of state it doesn't need access to.*codec.Codec
- This is a pointer to the codec that is used by Amino to encode and decode binary structs.sdk.StoreKey
- This gates access to asdk.KVStore
that persists state from transactions into the underlying network.
- This module has 3 store keys:
namesStoreKey
- This is the main store that stores the value string that the name points to (i.e.map[name]value
)ownersStoreKey
- This store contains the current owner of this name (i.e.map[sdk_address]name
)priceStoreKey
- This store contains the price that the current owner paid. Anyone buying this name must spend more than the current owner. (i.e.map[name]price
)
Now it is time to add methods to interract with the store via the Keeper
. First, add a function to set the string a given name resolves to:
// SetName - sets the value string that a name resolves to
func (k Keeper) SetName(ctx sdk.Context, name string, value string) {
store := ctx.KVStore(k.namesStoreKey)
store.Set([]byte(name), []byte(value))
}
In this method on the Keeper, first get the store object for the map[name]value
using the the namesStoreKey
from the Keeper.
NOTE: This function uses the
sdk.Context
. This object holds functions to access a number of import pieces of state likeblockHeight
andchainID
. See the godoc for more information on the methods it exposes.
Next, insert the <name, value>
pair into the store using its .Set([]byte, []byte)
method. As the store only takes []byte
, first cast the string
s to []byte
and the use them as parameters into the Set
method.
Next, add a method to resolve the names (i.e. look up the value for the name):
// ResolveName - returns the string that the name resolves to
func (k Keeper) ResolveName(ctx sdk.Context, name string) string {
store := ctx.KVStore(k.namesStoreKey)
bz := store.Get([]byte(name))
return string(bz)
}
Here, like in the SetName method, first access the store using the StoreKey
. Next, instead of using the Set
method on the store key, use the .Get([]byte) []byte
method. As the parameter into the function, pass the key, which is the name
string casted to []byte
, and get back the result in the form of []byte
. Cast this to a string
and return the result.
Add similar functions for getting and setting name owners:
// HasOwner - returns whether or not the name already has an owner
func (k Keeper) HasOwner(ctx sdk.Context, name string) bool {
store := ctx.KVStore(k.ownersStoreKey)
bz := store.Get([]byte(name))
return bz != nil
}
// GetOwner - get the current owner of a name
func (k Keeper) GetOwner(ctx sdk.Context, name string) sdk.AccAddress {
store := ctx.KVStore(k.ownersStoreKey)
bz := store.Get([]byte(name))
return bz
}
// SetOwner - sets the current owner of a name
func (k Keeper) SetOwner(ctx sdk.Context, name string, owner sdk.AccAddress) {
store := ctx.KVStore(k.ownersStoreKey)
store.Set([]byte(name), owner)
}
Notes on the above code:
- Instead of accessing the the data from the
namesStoreKey
store, get it from theownersStoreKey
store. - Because
sdk.AccAddress
is a type alias for[]byte
, and can natively cast to it. - There is an extra function,
HasOwner
that is a convenience to be used in conditional statements.
Finally, add a getter and setter for the price of a name:
// GetPrice - gets the current price of a name. If price doesn't exist yet, set to 1steak.
func (k Keeper) GetPrice(ctx sdk.Context, name string) sdk.Coins {
if !k.HasOwner(ctx, name) {
return sdk.Coins{sdk.NewInt64Coin("mycoin", 1)}
}
store := ctx.KVStore(k.pricesStoreKey)
bz := store.Get([]byte(name))
var price sdk.Coins
k.cdc.MustUnmarshalBinary(bz, &price)
return price
}
// SetPrice - sets the current price of a name
func (k Keeper) SetPrice(ctx sdk.Context, name string, price sdk.Coins) {
store := ctx.KVStore(k.pricesStoreKey)
store.Set([]byte(name), k.cdc.MustMarshalBinary(price))
}
Notes on the above code:
sdk.Coins
does not have its own bytes encoding, which means the price needs to be marsalled and unmarshalled using Amino to be inserted or removed from the store.- When getting the price for a name that has no owner (and thus no price), return 1steak as the price.
The last piece of code needed in the ./x/nameservice/keeper.go
file is a constructor function for Keeper
:
// NewKeeper creates new instances of the nameservice Keeper
func NewKeeper(coinKeeper bank.Keeper, namesStoreKey sdk.StoreKey, ownersStoreKey sdk.StoreKey, priceStoreKey sdk.StoreKey, cdc *codec.Codec) Keeper {
return Keeper{
coinKeeper: coinKeeper,
namesStoreKey: namesStoreKey,
ownersStoreKey: ownersStoreKey,
pricesStoreKey: priceStoreKey,
cdc: cdc,
}
}