Skip to content

Latest commit

 

History

History
147 lines (115 loc) · 7.15 KB

keeper.md

File metadata and controls

147 lines (115 loc) · 7.15 KB

The Keeper

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.

Keeper Struct

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 different cosmos-sdk packages:
    • codec - provides tools to work with the Cosmos encoding format, Amino
    • bank - the bank module controls accounts and coin transfers
    • types - contains commonly used types throughout the SDK
  • 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 a sdk.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)

Getters and Setters

Next its time to add some ways for your module to access and modify the stores held in the Keeper. As a first example, add a function to set the string that a 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 like blockHeight and chainID. 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 strings 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 the ownersStoreKey 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 a native []byte encoding. To marshal and unmarshal, use Amino through the codec.
  • 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,
	}
}

Next its time to move onto describing how users interact with your new store using Msgs and Handlers