Creating posts
In this chapter, we will be focusing on the process of handling a "create post" message. This involves the use of a special type of function known as a keeper method. Keeper methods are responsible for interacting with the blockchain and modifying its state based on the instructions provided in a message.
When a "create post" message is received, the corresponding keeper method will be called and passed the message as an argument. The keeper method can then use the various getter and setter functions provided by the store object to retrieve and modify the current state of the blockchain. This allows the keeper method to effectively process the "create post" message and make the necessary updates to the blockchain.
In order to keep the code for accessing and modifying the store object clean and
separate from the logic implemented in the keeper methods, we will create a new
file called post.go
. This file will contain functions that are specifically
designed to handle operations related to creating and managing posts within the
blockchain.
Appending posts to the store
package keeper
import (
"encoding/binary"
"blog/x/blog/types"
"github.com/cosmos/cosmos-sdk/store/prefix"
sdk "github.com/cosmos/cosmos-sdk/types"
)
func (k Keeper) AppendPost(ctx sdk.Context, post types.Post) uint64 {
count := k.GetPostCount(ctx)
post.Id = count
store := prefix.NewStore(ctx.KVStore(k.storeKey), types.KeyPrefix(types.PostKey))
appendedValue := k.cdc.MustMarshal(&post)
store.Set(GetPostIDBytes(post.Id), appendedValue)
k.SetPostCount(ctx, count+1)
return count
}
This code defines a function called AppendPost
which belongs to a Keeper
type. The Keeper
type is responsible for interacting with the blockchain and
modifying its state in response to various messages.
The AppendPost
function takes in two arguments: a Context
object and a
Post
object. The Context
object is a standard parameter in many functions in the Cosmos SDK and is used
to provide contextual information about the current state of the blockchain,
such as the current block height. The Post
object represents a post that will
be added to the blockchain.
The function begins by retrieving the current post count using the
GetPostCount
method. You will implement this method in the next step as it has
not been implemented yet. This method is called on the Keeper
object and takes
in a Context
object as an argument. It returns the current number of posts
that have been added to the blockchain.
Next, the function sets the ID of the new post to be the current post count, so
that each post has a unique identifier. It does this by assigning the value of
count to the Id
field of the Post
object.
The function then creates a new
store object using the
prefix.NewStore
function. The prefix.NewStore
function takes in two
arguments: the KVStore
associated with the provided context and a key prefix
for the Post
objects. The KVStore
is a key-value store that is used to
persist data on the blockchain, and the key prefix is used to differentiate the
Post
objects from other types of objects that may be stored in the same
KVStore
.
The function then serializes the Post
object using the cdc.MustMarshal
function and stores it in the blockchain using the Set
method of the store
object. The cdc.MustMarshal
function is part of the Cosmos SDK's
encoding/decoding library and
is used to convert the Post
object into a byte slice that can be stored in the
KVStore
. The Set
method is called on the store object and takes in two
arguments: a key and a value. In this case, the key is a byte slice generated by
the GetPostIDBytes
function and the value is the serialized Post
object. You
will implement this method in the next step as it has not been implemented yet.
Finally, the function increments the post count by one and updates the
blockchain state using the SetPostCount
method. You will implement this method
in the next step as it has not been implemented yet. This method is called on
the Keeper object and takes in a Context
object and a new post count as
arguments. It updates the current post count in the blockchain to be the new
post count provided.
The function then returns the ID of the newly created post, which is the current post count before it was incremented. This allows the caller of the function to know the ID of the post that was just added to the blockchain.
To complete the implementation of AppendPost
, the following tasks need to be
performed:
- Define
PostKey
, which will be used to store and retrieve posts from the database. - Implement
GetPostCount
, which will retrieve the current number of posts stored in the database. - Implement
GetPostIDBytes
, which will convert a post ID to a byte array. - Implement
SetPostCount
, which will update the post count stored in the database.
Post key prefix
In the file keys.go
, let's define the PostKey
prefix as follows:
const (
PostKey = "Post/value/"
)
This prefix will be used to uniquely identify a post within the system. It will be used as the beginning of the key for each post, followed by the ID of the post to create a unique key for each post.
Getting the post count
In the file post.go
, let's define the GetPostCount
function as follows:
func (k Keeper) GetPostCount(ctx sdk.Context) uint64 {
store := prefix.NewStore(ctx.KVStore(k.storeKey), []byte{})
byteKey := types.KeyPrefix(types.PostCountKey)
bz := store.Get(byteKey)
if bz == nil {
return 0
}
return binary.BigEndian.Uint64(bz)
}
This code defines a function named GetPostCount
that belongs to the Keeper
struct. The function takes in a single argument, a context object ctx
of type
sdk.Context
, and returns a value of type uint64
.
The function begins by creating a new store using the key-value store in the
context and an empty byte slice as the prefix. It then defines a byte slice
byteKey
using the KeyPrefix
function from the types
package, which takes
in the PostCountKey
. You will define PostCountKey
in the next step.
The function then retrieves the value at the key byteKey
in the store using
the Get
method and stores it in a variable bz
.
Next, the function checks if the value at byteKey
is nil
using an if
statement. If it is nil
, meaning that the key does not exist in the store, the
function returns 0. This indicates that there are no elements or posts
associated with the key.
If the value at byteKey
is not nil, the function uses the binary
package's
BigEndian
type to parse the bytes in bz
and returns the resulting uint64
value. The BigEndian
type is used to interpret the bytes in bz
as a
big-endian encoded unsigned 64-bit integer. The Uint64
method converts the
bytes to a uint64
value and returns it.
GetPostCount
function is used to retrieve the total number of posts stored in
the key-value store, represented as a uint64
value.
In the file keys.go
, let's define the PostCountKey
as follows:
const (
PostCountKey = "Post/count/"
)
This key will be used to keep track of the ID of the latest post added to the store.
Converting post ID to bytes
Now, let's implement GetPostIDBytes
, which will convert a post ID to a byte
array.
func GetPostIDBytes(id uint64) []byte {
bz := make([]byte, 8)
binary.BigEndian.PutUint64(bz, id)
return bz
}
GetPostIDBytes
takes in a value id
of type uint64
and returns a value of
type []byte
.
The function starts by creating a new byte slice bz
with a length of 8 using
the make
built-in function. It then uses the binary
package's BigEndian
type to encode the value of id
as a big-endian encoded unsigned integer and
store the result in bz
using the PutUint64
method. Finally, the function
returns the resulting byte slice bz
.
This function can be used to convert a post ID, represented as a uint64
, to a
byte slice that can be used as a key in a key-value store. The
binary.BigEndian.PutUint64
function encodes the uint64
value of id
as a
big-endian encoded unsigned integer and stores the resulting bytes in the
[]byte
slice bz
. The resulting byte slice can then be used as a key in the
store.
Updating the post count
Implement SetPostCount
in post.go
, which will update the post count stored
in the database.
func (k Keeper) SetPostCount(ctx sdk.Context, count uint64) {
store := prefix.NewStore(ctx.KVStore(k.storeKey), []byte{})
byteKey := types.KeyPrefix(types.PostCountKey)
bz := make([]byte, 8)
binary.BigEndian.PutUint64(bz, count)
store.Set(byteKey, bz)
}
This code defines a function SetPostCount
in the Keeper
struct. The function
takes in a context ctx
of type sdk.Context
and a value count
of type
uint64
, and does not return a value.
The function first creates a new store by calling the NewStore
function from
the prefix package and passing in the key-value store from the context and an
empty byte slice as the prefix. It stores the resulting store in a variable
named store
.
Next, the function defines a byte slice byteKey
using the KeyPrefix
function
from the types
package and passing in the PostCountKey
. The KeyPrefix
function returns a byte slice with the given key as a prefix.
The function then creates a new byte slice bz
with a length of 8 using the
make
built-in function. It then uses the binary
package's BigEndian
type
to encode the value of count as a big-endian encoded unsigned integer and store
the result in bz
using the PutUint64
method.
Finally, the function calls the Set
method on the store
variable, passing in
byteKey
and bz
as arguments. This sets the value at the key byteKey
in the
store to the value bz
.
This function can be used to update the count of posts stored in the database.
It does this by converting the uint64
value of count to a byte slice using the
binary.BigEndian.PutUint64
function, and then storing the resulting byte slice
at the key byteKey
in the store using the Set
method.
Now that you have implemented the code for creating blog posts, you can proceed to implement the keeper method that is invoked when the "create post" message is processed.
Handling the "create post" message
package keeper
import (
"context"
"blog/x/blog/types"
sdk "github.com/cosmos/cosmos-sdk/types"
)
func (k msgServer) CreatePost(goCtx context.Context, msg *types.MsgCreatePost) (*types.MsgCreatePostResponse, error) {
ctx := sdk.UnwrapSDKContext(goCtx)
var post = types.Post{
Creator: msg.Creator,
Title: msg.Title,
Body: msg.Body,
}
id := k.AppendPost(
ctx,
post,
)
return &types.MsgCreatePostResponse{
Id: id,
}, nil
}
The CreatePost
function is a message handler for the MsgCreatePost
message
type. It is responsible for creating a new post on the blockchain based on the
information provided in the MsgCreatePost
message.
The function first retrieves the Cosmos SDK context from the Go context using
the sdk.UnwrapSDKContext
function. It then creates a new Post
object using
the Creator
, Title
, and Body
fields from the MsgCreatePost message.
Next, the function calls the AppendPost
method on the msgServer
object
(which is of the Keeper type) and passes in the Cosmos SDK context and the new
Post
object as arguments. The AppendPost
method is responsible for adding
the new post to the blockchain and returning the ID of the new post.
Finally, the function returns a MsgCreatePostResponse
object that contains the
ID of the new post. It also returns a nil error, indicating that the operation
was successful.
Summary
Great job! You have successfully implemented the logic for writing blog posts to the blockchain store and the keeper method that will be called when a "create post" message is processed.
The AppendPost
keeper method retrieves the current post count, sets the ID of
the new post to be the current post count, serializes the post object, and
stores it in the blockchain using the Set
method of the store
object. The
key for the post in the store is a byte slice generated by the GetPostIDBytes
function and the value is the serialized post object. The function then
increments the post count by one and updates the blockchain state using the
SetPostCount
method.
The CreatePost
handler method receives a MsgCreatePost
message containing
the data for the new post, creates a new Post
object using this data, and
passes it to the AppendPost
keeper method to be added to the blockchain. It
then returns a MsgCreatePostResponse
object containing the ID of the newly
created post.
By implementing these methods, you have successfully implemented the necessary logic for handling "create post" messages and adding posts to the blockchain.