Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

docs: upgrade build a blog to v0.19.2 and make edits #1987

Merged
merged 8 commits into from
Jan 28, 2022
Merged
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
155 changes: 107 additions & 48 deletions docs/guide/blog/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,14 @@ By completing this tutorial, you will learn about:

**Note:** All the functions in this chapter can be scaffolded with a single command but instead we will learn how to add each functionality individually.

## Prerequisites

This series of blog tutorials is based on a specific version of Starport, so to install Starport v0.19.2 use the following command:

```bash
curl https://get.starport.network/starport@v0.19.2! | bash
```

## Create Your Blog Chain

First, create a new blockchain.
Expand All @@ -34,7 +42,7 @@ starport scaffold chain github.com/cosmonaut/blog

The `blog` directory is created with the default directory structure.

## High Level Transaction Review
## High-Level Transaction Review

So far, you have learned how to modify proto files to define a new API endpoint and modify a keeper query function to return static data back to the user. Of course, a keeper can do more than return a string of data. Its purpose is to manage access to the state of the blockchain.

Expand All @@ -60,15 +68,22 @@ The `message` command accepts message name (`createPost`) and a list of fields (

The `message` command has created and modified several files:

- modified `proto/blog/tx.proto`
- modified `x/blog/handler.go`
- created `x/blog/keeper/msg_server_create_post.go`
- modified `x/blog/client/cli/tx.go`
- created `x/blog/client/cli/tx_create_post.go`
- created `x/blog/types/message_create_post.go`
- modified `x/blog/types/codec.go`
```bash
modify proto/blog/tx.proto
modify x/blog/client/cli/tx.go
create x/blog/client/cli/tx_create_post.go
modify x/blog/handler.go
create x/blog/keeper/msg_server_create_post.go
modify x/blog/module_simulation.go
create x/blog/simulation/create_post.go
modify x/blog/types/codec.go
create x/blog/types/message_create_post.go
create x/blog/types/message_create_post_test.go

🎉 Created a message `createPost`.
```

As always, start with a proto file. Inside the `proto/blog/tx.proto` file, the `MsgCreatePost` message has been created. Edit the file to define the id for `message MsgCreatePostResponse`:
As always, start with a proto file. Inside the `proto/blog/tx.proto` file, the `MsgCreatePost` message has been created. Edit the file to add the line that defines the `id` for `message MsgCreatePostResponse`:

```go
message MsgCreatePost {
Expand All @@ -82,7 +97,9 @@ message MsgCreatePostResponse {
}
```

First, define a Cosmos SDK message type with proto `message`. The `MsgCreatePost` has three fields: creator, title and body. Since the purpose of the `MsgCreatePost` message is to create new posts in the store, the only thing the message needs to return is an ID of a created post. The `CreatePost` rpc was already added to the `Msg` service:
## Review the Message Code

Review the Cosmos SDK message type with proto `message`. The `MsgCreatePost` has three fields: creator, title and body. Since the purpose of the `MsgCreatePost` message is to create new posts in the store, the only thing the message needs to return is an ID of a created post. The `CreatePost` rpc was already added to the `Msg` service:

```go
service Msg {
Expand Down Expand Up @@ -111,7 +128,7 @@ The `case *types.MsgCreatePost` statement handles messages of type `MsgCreatePos

Every module has a handler function like this to process messages and call keeper methods.

## Process Messages
## Define Messages

In the newly scaffolded `x/blog/keeper/msg_server_create_post.go` file, you can see a placeholder implementation of the `CreatePost` function. Right now it does nothing and returns an empty response. For your blog chain, you want the contents of the message (title and body) to be written to the state as a new post.

Expand All @@ -126,9 +143,9 @@ func (k msgServer) CreatePost(goCtx context.Context, msg *types.MsgCreatePost) (
ctx := sdk.UnwrapSDKContext(goCtx)
// Create variable of type Post
var post = types.Post{
Creator: msg.Creator,
Title: msg.Title,
Body: msg.Body,
Creator: msg.Creator,
Title: msg.Title,
Body: msg.Body,
}
// Add a post to the store and get back the ID
id := k.AppendPost(ctx, post)
Expand Down Expand Up @@ -158,28 +175,36 @@ message Post {
}
```

The contents of the `post.proto` file are fairly standard. The file defines a package name that is used to identify messages, among other things, specifies the Go package where new files are generated, and finally defines `message Post`.
The contents of the `post.proto` file are standard. The file defines:

- A package name `cosmonaut.blog.blog` that is used to identify messages
- The Go package `go_package = "github.com/cosmonaut/blog/x/blog/types"` where new files are generated
- The message `message Post`

Each file save triggers an automatic rebuild. Now, after you build and start your chain with Starport, the `Post` type is available.
Continue developing your blog chain.

### Define Keeper Methods

The next step is to define the `AppendPost` keeper method. Create the `x/blog/keeper/post.go` file and start thinking about the logic of the function.
The next step is to define the `AppendPost` keeper method.

To implement `AppendPost` you must first understand how the store works. You can think of a store as a key-value database where keys are lexicographically ordered. You can loop through keys and use `Get` and `Set` to retrieve and set values based on keys. To distinguish between different types of data that a module can keep in its store, you can use prefixes like `product-` or `post-`.
Create the `x/blog/keeper/post.go` file and start thinking about the logic of the function and what you want to call the prefixes. The file will be empty for now.

To keep a list of posts in what is essentially a key-value store, you need to keep track of the index of the posts you insert. Since both post values and post count (index) values are kept in the store, you can use different prefixes: `Post-value-` and `Post-count-`.
- To implement `AppendPost` you must first understand how the key store works. You can think of a store as a key-value database where keys are lexicographically ordered. You can loop through keys and use `Get` and `Set` to retrieve and set values based on keys. To distinguish between different types of data that a module can keep in its store, you can use prefixes like `product-` or `post-`.

Add these prefixes to the `x/blog/types/keys.go` file:
- To keep a list of posts in what is essentially a key-value store, you need to keep track of the index of the posts you insert. Since both post values and post count (index) values are kept in the store, you can use different prefixes: `Post-value-` and `Post-count-`.

Then, add these prefixes to the `x/blog/types/keys.go` file in the `const` and add a comment that describes the keys:

```go
const (
//...
// Keep track of the index of posts
PostKey = "Post-value-"
PostCountKey = "Post-count-"
)
```

When a `Post` message is sent to the `AppendPost` function, four actions occur:
Your blog is now updated to take these actions when a `Post` message is sent to the `AppendPost` function:

- Get the number of posts in the store (count)
- Add a post by using the count as an ID
Expand All @@ -188,7 +213,7 @@ When a `Post` message is sent to the `AppendPost` function, four actions occur:

## Write Data to the Store

Now, after the `import` section, draft the `AppendPost` function in the `x/blog/keeper/post.go` file:
Now, after the `import` section in the `x/blog/keeper/post.go` file, draft the `AppendPost` function. You can add these comments to help you visualize what you do next:

```go
// func (k Keeper) AppendPost() uint64 {
Expand Down Expand Up @@ -234,7 +259,7 @@ func (k Keeper) SetPostCount(ctx sdk.Context, count uint64) {
}
```

Now that you have implemented functions for getting the number of posts and setting the post count, you can implement the logic behind the `AppendPost` function:
Now that you have implemented functions for getting the number of posts and setting the post count, at the top of the same `x/blog/keeper/post.go` file, implement the logic behind the `AppendPost` function:

```go
package keeper
Expand Down Expand Up @@ -269,23 +294,34 @@ func (k Keeper) AppendPost(ctx sdk.Context, post types.Post) uint64 {

By following these steps, you have implemented all of the code required to create new posts and store them on-chain. Now, when a transaction that contains a message of type `MsgCreatePost` is broadcast, the message is routed to your blog module.

- `x/blog/handler.go` calls `k.CreatePost` which in turn calls `AppendPost`.
- `AppendPost` gets the number of posts from the store, adds a post using the count as an ID, increments the count, and returns the ID.
- `x/blog/handler.go` calls `k.CreatePost` which in turn calls `AppendPost`
- `AppendPost` gets the number of posts from the store, adds a post using the count as an ID, increments the count, and returns the ID

## Create a Post

Try it out! If the chain is yet not started, run `starport chain serve`.
Try it out! Start your chain:

```go
starport chain serve
```

Create a post:

```bash
blogd tx blog create-post foo bar --from alice
```

The command output shows the transaction and asks you to sign the transaction:

```bash
"body":{"messages":[{"@type":"/cosmonaut.blog.blog.MsgCreatePost","creator":"cosmos1dad8xvsj3dse928r52yayygghwvsggvzlm730p","title":"foo","body":"bar"}],"memo":"","timeout_height":"0","extension_options":[],"non_critical_extension_options":[]},"auth_info":{"signer_infos":[],"fee":{"amount":[],"gas_limit":"200000","payer":"","granter":""}},"signatures":[]}

confirm transaction before signing and broadcasting [y/N]: y
```

Type `y`. The transaction is output to the terminal:

```bash
{"height":"6861","txhash":"6086372860704F5F88F4D0A3CF23523CF6DAD2F637E4068B92582E3BB13800DA","codespace":"","code":0,"data":"0A100A0A437265617465506F737412020801","raw_log":"[{\"events\":[{\"type\":\"message\",\"attributes\":[{\"key\":\"action\",\"value\":\"CreatePost\"}]}]}]","logs":[{"msg_index":0,"log":"","events":[{"type":"message","attributes":[{"key":"action","value":"CreatePost"}]}]}],"info":"","gas_wanted":"200000","gas_used":"44674","tx":null,"timestamp":""}
```

Expand All @@ -304,26 +340,35 @@ Two components are responsible for querying data:

First, review the services and messages in `proto/blog/query.proto`. The `Posts` rpc accepts an empty request and returns an object with two fields: title and body. Now you can make changes so it can return a list of posts. The list of posts can be long, so add pagination. When pagination is added, the request and response include a page number so you can request a particular page when you know what page has been returned.

In the `proto/blog/query.proto` file:
To define the types in proto files, make the following updates in `proto/blog/query.proto`:

```go
// Import the Post message
import "blog/post.proto";
1. Add the `import`:

message QueryPostsRequest {
// Adding pagination to request
cosmos.base.query.v1beta1.PageRequest pagination = 1;
}
```go
import "blog/post.proto";
```

message QueryPostsResponse {
// Returning a list of posts
repeated Post Post = 1;
// Adding pagination to response
cosmos.base.query.v1beta1.PageResponse pagination = 2;
}
```
2. Add pagination to the post request:

After the types are defined in proto files, you can implement post querying logic. In `grpc_query_posts.go`:
```go
message QueryPostsRequest {
// Adding pagination to request
cosmos.base.query.v1beta1.PageRequest pagination = 1;
}
```

3. Add pagination to the post response:

```go
message QueryPostsResponse {
// Returning a list of posts
repeated Post Post = 1;
// Adding pagination to response
cosmos.base.query.v1beta1.PageResponse pagination = 2;
}
```

To implement post querying logic in the `grpc_query_posts.go` file, delete the contents of that file and replace it with:

```go
package keeper
Expand Down Expand Up @@ -374,16 +419,16 @@ func (k Keeper) Posts(c context.Context, req *types.QueryPostsRequest) (*types.Q

In the `x/blog/module.go` file:

1. Add `"context"` to the imports.
1. Add `"context"` to the imports, don't save the file yet.

```go
import (
"context"
"context"
// ... other imports
)
```

2. Register the query handler:
2. Update the `RegisterGRPCGatewayRoutes` function to register the query handler client:

```go
// RegisterGRPCGatewayRoutes registers the gRPC Gateway routes for the module.
Expand All @@ -392,30 +437,44 @@ In the `x/blog/module.go` file:
}
```

3. Now that you've modified the file with the two updates, now it's safe to save the file.

## Use the CLI To Create And Display Posts
## Use the CLI to Create a Post

Now that you have implemented logic for creating and querying posts, you can use the node's binary to interact with your chain. Your blog chain binary is `blogd`.
Now that you have implemented logic for creating and querying posts, you can interact with your blog chain using the command line. The blog chain binary is `blogd`.

To create a post using the command line:
To create a post at the command line:

```bash
blogd tx blog create-post foo bar --from alice
```

The transaction is output to the terminal. You are prompted to confirm the transaction:

```bash
{"body":{"messages":[{"@type":"/cosmonaut.blog.blog.MsgCreatePost","creator":"cosmos1c9zy9aajk9fs2f8ygtz4pm22r3rxmg597vw2n3","title":"foo","body":"bar"}],"memo":"","timeout_height":"0","extension_options":[],"non_critical_extension_options":[]},"auth_info":{"signer_infos":[],"fee":{"amount":[],"gas_limit":"200000","payer":"","granter":""}},"signatures":[]}

confirm transaction before signing and broadcasting [y/N]: y
```

Type `y` to sign the transaction:

```bash
{"height":"2828","txhash":"E04A712E65B0F6F30F5DC291A6552B69F6CB3F77761F28AFFF8EAA535EC4C589","codespace":"","code":0,"data":"0A100A0A437265617465506F737412020801","raw_log":"[{\"events\":[{\"type\":\"message\",\"attributes\":[{\"key\":\"action\",\"value\":\"CreatePost\"}]}]}]","logs":[{"msg_index":0,"log":"","events":[{"type":"message","attributes":[{"key":"action","value":"CreatePost"}]}]}],"info":"","gas_wanted":"200000","gas_used":"44674","tx":null,"timestamp":""}
```

Congratulations, you built a chain binary and used the `blogd` binary CLI to create a blog post.

## Use the CLI to Query a Post

To query the list of all on-chain posts:

```bash
blogd q blog posts
```

The result:

```bash
Post:
- body: bar
Expand Down