Skip to content

Commit e281bdc

Browse files
authored
docs: collections docs + minor nits (#4628)
1 parent 121c3a8 commit e281bdc

7 files changed

Lines changed: 313 additions & 125 deletions

File tree

docs/docs/01-welcome/03-tutorials.md

Lines changed: 0 additions & 40 deletions
This file was deleted.

docs/docs/02-guide/03-hello-world.md

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ This command generates code for a new query, `say-hello`, which accepts a name,
3737
- **Understanding the Scaffolded Code:**
3838

3939
- `proto/hello/hello/query.proto`: Defines the request and response structure.
40-
- `x/hello/client/cli/query_say_hello.go`: Contains the CLI commands for the query.
40+
- `x/hello/module/autocli.go`: Contains commands for the query, using [AutoCLI](../08-references/04-glossary.md#autocli).
4141
- `x/hello/keeper/query_say_hello.go`: Houses the logic for the query response.
4242

4343
## Customizing the Query Response
@@ -53,11 +53,9 @@ package keeper
5353

5454
import (
5555
"context"
56-
"fmt"
5756

5857
"hello/x/hello/types"
5958

60-
sdk "github.com/cosmos/cosmos-sdk/types"
6159
"google.golang.org/grpc/codes"
6260
"google.golang.org/grpc/status"
6361
)
@@ -67,10 +65,8 @@ func (q queryServer) SayHello(ctx context.Context, req *types.QuerySayHelloReque
6765
return nil, status.Error(codes.InvalidArgument, "invalid request")
6866
}
6967

70-
// Validation and Context unwrapping
71-
sdkCtx := sdk.UnwrapSDKContext(ctx)
68+
// TODO: Process the query
7269

73-
_ = sdkCtx
7470
// Custom Response
7571
return &types.QuerySayHelloResponse{Name: fmt.Sprintf("Hello %s!", req.Name)}, nil
7672
}

docs/docs/02-guide/05-debug.md

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -41,10 +41,10 @@ Use the
4141
`<filename>:<line>` notation:
4242

4343
```
44-
(dlv) break x/hello/client/cli/query_say_hello.go:14
44+
(dlv) break x/hello/keeper/query_say_hello.go:13
4545
```
4646

47-
This command adds a breakpoint to the `x/hello/client/cli/query_say_hello.go`
47+
This command adds a breakpoint to the `x/hello/keeper/query_say_hello.go`
4848
file at line 14.
4949

5050
Once all breakpoints are set resume blockchain execution using the
@@ -183,17 +183,16 @@ hellod query hello say-hello bob
183183
A debugger shell will be launched when the breakpoint is triggered:
184184

185185
```
186-
7: "google.golang.org/grpc/codes"
187-
8: "google.golang.org/grpc/status"
188-
9: "hello/x/hello/types"
189-
10: )
186+
7: "google.golang.org/grpc/codes"
187+
8: "google.golang.org/grpc/status"
188+
9: "hello/x/hello/types"
189+
10: )
190190
11:
191-
=> 12: func (k Keeper) SayHello(goCtx context.Context, req *types.QuerySayHelloRequest) (*types.QuerySayHelloResponse, error) {
192-
13: if req == nil {
193-
14: return nil, status.Error(codes.InvalidArgument, "invalid request")
194-
15: }
191+
=> 12: func (k Keeper) SayHello(ctx context.Context, req *types.QuerySayHelloRequest) (*types.QuerySayHelloResponse, error) {
192+
13: if req == nil {
193+
14: return nil, status.Error(codes.InvalidArgument, "invalid request")
194+
15: }
195195
16:
196-
17: ctx := sdk.UnwrapSDKContext(goCtx)
197196
```
198197

199198
From then on you can use Delve commands like `next` (alias `n`) or `print`

docs/docs/02-guide/08-state.md

Lines changed: 230 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,230 @@
1+
---
2+
description: Learn how Cosmos SDK modules manage state with collections
3+
title: State Management
4+
---
5+
6+
# State Management in Modules
7+
8+
In blockchain applications, state refers to the current data stored on the blockchain at a specific point in time. Handling state is usually the core of any blockchain application. The Cosmos SDK provides powerful tools for state management, with the `collections` package being the recommended approach for modern applications.
9+
10+
## Collections Package
11+
12+
Ignite scaffolds using the [`collections`](http://pkg.go.dev/cosmossdk.io/collections) package for module code. This package provides a type-safe and efficient way to set and query values from the module store.
13+
14+
### Key Features of Collections
15+
16+
- **Type Safety**: Collections are type-safe, reducing the risk of runtime errors.
17+
- **Simplified API**: Easy-to-use methods for common operations like Get, Set, and Has.
18+
- **Performance**: Optimized for performance with minimal overhead.
19+
- **Integration**: Seamlessly integrates with the Cosmos SDK ecosystem.
20+
21+
## Understand keeper field
22+
23+
Ignite creates all the necessary boilerplate for collections in the `x/<module>/keeper/keeper.go` file. The `Keeper` struct contains fields for each collection you define in your module. Each field is an instance of a collection type, such as `collections.Map`, `collections.Item`, or `collections.List`.
24+
25+
```go
26+
type Keeper struct {
27+
// ...
28+
29+
Params collections.Item[Params]
30+
Counters collections.Map[string, uint64]
31+
Profiles collections.Map[sdk.AccAddress, Profile]
32+
}
33+
```
34+
35+
## Common State Operations
36+
37+
### Reading State
38+
39+
To read values from state, use the `Get` method:
40+
41+
```go
42+
// getting a single item
43+
params, err := k.Params.Get(ctx)
44+
if err != nil {
45+
// handle error
46+
// collections.ErrNotFound is returned when an item doesn't exist
47+
}
48+
49+
// getting a map entry
50+
counter, err := k.Counters.Get(ctx, "my-counter")
51+
if err != nil {
52+
// handle error
53+
}
54+
```
55+
56+
### Writing State
57+
58+
To write values to state, use the `Set` method:
59+
60+
```go
61+
// setting a single item
62+
err := k.Params.Set(ctx, params)
63+
if err != nil {
64+
// handle error
65+
}
66+
67+
// setting a map entry
68+
err = k.Counters.Set(ctx, "my-counter", 42)
69+
if err != nil {
70+
// handle error
71+
}
72+
```
73+
74+
### Checking Existence
75+
76+
Use the `Has` method to check if a value exists without retrieving it:
77+
78+
```go
79+
exists, err := k.Counters.Has(ctx, "my-counter")
80+
if err != nil {
81+
// handle error
82+
}
83+
if exists {
84+
// value exists
85+
}
86+
```
87+
88+
### Removing State
89+
90+
To remove values from state, use the `Remove` method:
91+
92+
```go
93+
err := k.Counters.Remove(ctx, "my-counter")
94+
if err != nil {
95+
// handle error
96+
}
97+
```
98+
99+
## Implementing Business Logic in Messages
100+
101+
Messages in Cosmos SDK modules modify state based on user transactions. Here's how to implement business logic in a message handler using collections:
102+
103+
```go
104+
func (k msgServer) CreateProfile(ctx context.Context, msg *types.MsgCreateProfile) (*types.MsgCreateProfileResponse, error) {
105+
// validate message
106+
if err := msg.ValidateBasic(); err != nil {
107+
return nil, err
108+
}
109+
110+
// parse sender address
111+
senderBz, err := k.addressCodec.StringToBytes(msg.Creator)
112+
if err != nil {
113+
return nil, err
114+
}
115+
sender := sdk.AccAddress(senderBz)
116+
117+
// check if profile already exists
118+
exists, err := k.Profiles.Has(ctx, sender)
119+
if err != nil {
120+
return nil, err
121+
}
122+
if exists {
123+
return nil, sdkerrors.Wrap(types.ErrProfileExists, "profile already exists")
124+
}
125+
126+
// create new profile
127+
sdkCtx := sdk.UnwrapSDKContext(ctx)
128+
profile := types.Profile{
129+
Name: msg.Name,
130+
Bio: msg.Bio,
131+
CreatedAt: sdkCtx.BlockTime().Unix(),
132+
}
133+
134+
// store the profile
135+
err = k.Profiles.Set(ctx, sender, profile)
136+
if err != nil {
137+
return nil, err
138+
}
139+
140+
// increment profile counter
141+
counter, err := k.Counters.Get(ctx, "profiles")
142+
if err != nil && !errors.Is(err, collections.ErrNotFound) {
143+
return nil, err
144+
}
145+
// set the counter (adding 1)
146+
err = k.Counters.Set(ctx, "profiles", counter+1)
147+
if err != nil {
148+
return nil, err
149+
}
150+
151+
return &types.MsgCreateProfileResponse{}, nil
152+
}
153+
```
154+
155+
## Implementing Queries
156+
157+
Queries allow users to read state without modifying it. Here's how to implement a query handler using collections:
158+
159+
```go
160+
func (q queryServer) GetProfile(ctx context.Context, req *types.QueryGetProfileRequest) (*types.QueryGetProfileResponse, error) {
161+
if req == nil {
162+
return nil, status.Error(codes.InvalidArgument, "invalid request")
163+
}
164+
165+
// parse address
166+
addressBz, err := k.addressCodec.StringToBytes(req.Address)
167+
if err != nil {
168+
return nil, status.Error(codes.InvalidArgument, "invalid address")
169+
}
170+
address := sdk.AccAddress(addressBz)
171+
172+
// get profile
173+
profile, err := q.k.Profiles.Get(ctx, address)
174+
if err != nil {
175+
if errors.Is(err, collections.ErrNotFound) {
176+
return nil, status.Error(codes.NotFound, "profile not found")
177+
}
178+
return nil, status.Error(codes.Internal, "internal error")
179+
}
180+
181+
return &types.QueryGetProfileResponse{Profile: profile}, nil
182+
}
183+
```
184+
185+
## Error Handling with Collections
186+
187+
When working with collections, proper error handling is essential:
188+
189+
```go
190+
// example from a query function
191+
params, err := q.k.Params.Get(ctx)
192+
if err != nil && !errors.Is(err, collections.ErrNotFound) {
193+
return nil, status.Error(codes.Internal, "internal error")
194+
}
195+
```
196+
197+
In the snippet above, it uses the `Get` method to get a collection item. A `collections.ErrNotFound` can be a valid error when the collection is empty, whereas any other error is considered an internal error that should be handled appropriately.
198+
199+
## Iterating Over Collections
200+
201+
Collections also support iteration:
202+
203+
```go
204+
// iterate over all profiles
205+
err := k.Profiles.Walk(ctx, nil, func(key sdk.AccAddress, value types.Profile) (bool, error) {
206+
// process each profile
207+
// return true to stop iteration, false to continue
208+
return false, nil
209+
})
210+
if err != nil {
211+
// handle error
212+
}
213+
214+
// iterate over a range of counters
215+
startKey := "a"
216+
endKey := "z"
217+
err = k.Counters.Walk(ctx, collections.NewPrefixedPairRange[string, uint64](startKey, endKey), func(key string, value uint64) (bool, error) {
218+
// process each counter in the range
219+
return false, nil
220+
})
221+
if err != nil {
222+
// handle error
223+
}
224+
```
225+
226+
## Conclusion
227+
228+
The `collections` package provides a powerful and type-safe way to manage state in Cosmos SDK modules. By understanding how to use collections effectively, you can build robust and efficient blockchain applications that handle state transitions reliably.
229+
230+
When developing with Ignite CLI, you are already taking advantage of collections which significantly simplify the state management code and reduce the potential for errors.

0 commit comments

Comments
 (0)