-
Notifications
You must be signed in to change notification settings - Fork 3.6k
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
Add SDK_By_Examples to doc #1795
Changes from 6 commits
01ba6c3
d41b275
64fb2bf
30e9ba4
3de9196
54b526c
d7ce5ed
bacb74a
bb1bd22
d8369b9
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
**SDK by Examples** offers an alternative and complementary way to learn about the Cosmos-SDK. It contains several examples that showcase how to build a decentralised application on top of the Cosmos-SDK from start to finish. | ||
|
||
Without further due, let us get into it! | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
|
||
- [Simple governance example](./simple-governance/intro.md) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. only one example for to start? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. yup, when we have other examples we'll add them to the list |
||
|
||
If you have an example you would like to add to the list, feel free to open a PR [here](https://github.com/cosmos/cosmos-sdk/pulls). |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
## Application CLI | ||
|
||
**File: [`cmd/simplegovcli/maing.go`](https://github.com/cosmos/cosmos-sdk/blob/fedekunze/module_tutorial/examples/simpleGov/cmd/simplegovcli/main.go)** | ||
|
||
To interact with our application, let us add the commands from the `simple_governance` module to our `simpleGov` application, as well as the pre-built SDK commands: | ||
|
||
```go | ||
// cmd/simplegovcli/main.go | ||
... | ||
rootCmd.AddCommand( | ||
client.GetCommands( | ||
simplegovcmd.GetCmdQueryProposal("proposals", cdc), | ||
simplegovcmd.GetCmdQueryProposals("proposals", cdc), | ||
simplegovcmd.GetCmdQueryProposalVotes("proposals", cdc), | ||
simplegovcmd.GetCmdQueryProposalVote("proposals", cdc), | ||
)...) | ||
rootCmd.AddCommand( | ||
client.PostCommands( | ||
simplegovcmd.PostCmdPropose(cdc), | ||
simplegovcmd.PostCmdVote(cdc), | ||
)...) | ||
... | ||
``` |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
## Application codec | ||
|
||
**File: [`app/app.go`](https://github.com/cosmos/cosmos-sdk/blob/fedekunze/module_tutorial/examples/simpleGov/app/app.go)** | ||
|
||
Finally, we need to define the `MakeCodec()` function and register the concrete types and interface from the various modules. | ||
|
||
```go | ||
func MakeCodec() *wire.Codec { | ||
var cdc = wire.NewCodec() | ||
wire.RegisterCrypto(cdc) // Register crypto. | ||
sdk.RegisterWire(cdc) // Register Msgs | ||
bank.RegisterWire(cdc) | ||
simplestake.RegisterWire(cdc) | ||
simpleGov.RegisterWire(cdc) | ||
|
||
// Register AppAccount | ||
cdc.RegisterInterface((*auth.Account)(nil), nil) | ||
cdc.RegisterConcrete(&types.AppAccount{}, "simpleGov/Account", nil) | ||
return cdc | ||
} | ||
``` |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
## App commands | ||
|
||
We will need to add the newly created commands to our application. To do so, go to the `cmd` folder inside your root directory: | ||
|
||
```bash | ||
// At root level of directory | ||
cd cmd | ||
``` | ||
`simplegovd` is the folder that stores the command for running the server daemon, whereas `simplegovcli` defines the commands of your application. |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
## Application constructor | ||
|
||
**File: [`app/app.go`](https://github.com/cosmos/cosmos-sdk/blob/fedekunze/module_tutorial/examples/simpleGov/app/app.go)** | ||
|
||
Now, we need to define the constructor for our application. | ||
|
||
```go | ||
func NewSimpleGovApp(logger log.Logger, db dbm.DB) *SimpleGovApp | ||
``` | ||
|
||
In this function, we will: | ||
|
||
- Create the codec | ||
|
||
```go | ||
var cdc = MakeCodec() | ||
``` | ||
|
||
- Instantiate our application. This includes creating the keys to access each of the substores. | ||
|
||
```go | ||
// Create your application object. | ||
var app = &SimpleGovApp{ | ||
BaseApp: bam.NewBaseApp(appName, cdc, logger, db), | ||
cdc: cdc, | ||
capKeyMainStore: sdk.NewKVStoreKey("main"), | ||
capKeyAccountStore: sdk.NewKVStoreKey("acc"), | ||
capKeyStakingStore: sdk.NewKVStoreKey("stake"), | ||
capKeySimpleGovStore: sdk.NewKVStoreKey("simpleGov"), | ||
} | ||
``` | ||
|
||
- Instantiate the keepers. Note that keepers generally need access to other module's keepers. In this case, make sure you only pass an instance of the keeper for the functionality that is needed. If a keeper only needs to read in another module's store, a read-only keeper should be passed to it. | ||
|
||
```go | ||
app.coinKeeper = bank.NewKeeper(app.accountMapper) | ||
app.stakeKeeper = simplestake.NewKeeper(app.capKeyStakingStore, app.coinKeeper,app.RegisterCodespace(simplestake.DefaultCodespace)) | ||
app.simpleGovKeeper = simpleGov.NewKeeper(app.capKeySimpleGovStore, app.coinKeeper, app.stakeKeeper, app.RegisterCodespace(simpleGov.DefaultCodespace)) | ||
``` | ||
|
||
- Declare the handlers. | ||
|
||
```go | ||
app.Router(). | ||
AddRoute("bank", bank.NewHandler(app.coinKeeper)). | ||
AddRoute("simplestake", simplestake.NewHandler(app.stakeKeeper)). | ||
AddRoute("simpleGov", simpleGov.NewHandler(app.simpleGovKeeper)) | ||
``` | ||
|
||
- Initialize the application. | ||
|
||
```go | ||
// Initialize BaseApp. | ||
app.MountStoresIAVL(app.capKeyMainStore, app.capKeyAccountStore, app.capKeySimpleGovStore, app.capKeyStakingStore) | ||
app.SetAnteHandler(auth.NewAnteHandler(app.accountMapper, app.feeCollectionKeeper)) | ||
err := app.LoadLatestVersion(app.capKeyMainStore) | ||
if err != nil { | ||
cmn.Exit(err.Error()) | ||
} | ||
return app | ||
``` |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,78 @@ | ||
## Application design | ||
|
||
### Application description | ||
|
||
For this tutorial, we will code a **simple governance application**, accompagnied by a **simple governance module**. It will allow us to explain most of the basic notions required to build a functioning application on the Cosmos-SDK. Note that this is not the governance module used for the Cosmos Hub. A much more [advanced governance module](https://github.com/cosmos/cosmos-sdk/tree/develop/x/gov) will be used instead. | ||
|
||
All the code for the `simple_governance` application can be found [here](https://github.com/gamarin2/cosmos-sdk/tree/module_tutorial/examples/simpleGov/x/simple_governance). You'll notice that the module and app aren't located at the root level of the repo but in the examples directory. This is just for convenience, you can code your module and application directly in the root directory. | ||
|
||
Without further talk, let's get into it! | ||
|
||
### Requirements | ||
|
||
We will start by writting down your module's requirements. We are designing a simple governance module, in which we want: | ||
|
||
- Simple text proposals, that any coin holder can submit. | ||
- Proposals must be submitted with a deposit in Atoms. If the deposit is larger than a `MinDeposit`, the associated proposal enters the voting period. Otherwise it is rejected. | ||
- Bonded Atom holders can vote on proposal on a 1 bonded Atom 1 vote basis | ||
- Bonded Atom holders can choose between 3 options when casting a vote: `Yes`, `No` and `Abstain`. | ||
- If, at the end of the voting period, there are more `Yes` votes than `No` votes, the proposal is accepted. Otherwise, it is rejected. | ||
- Voting period is 2 weeks | ||
|
||
When designing a module, it is good to adopt a certain methodology. Remember that a blockchain application is just a replicated state-machine. The state is the representation of the application at a given time. It is up to the application developer to define what the state represents, depending on the goal of the application. For example, the state of a simple cryptocurrency application will be a mapping of addresses to balances. | ||
|
||
The state can be updated according to predefined rules. Given a state and a transaction, the state-machine (i.e. the application) will return a new state. In a blockchain application, transactions are bundled in blocks, but the logic is the same. Given a state and a set of transactions (a block), the application returns a new state. A SDK-module is just a subset of the application, but it is based on the same principles. As a result, module developers only have to define a subset of the state and a subset of the transaction types, which trigger state transitions. | ||
|
||
In summary, we have to define: | ||
|
||
- A `State`, which represents a subset of the current state of the application. | ||
- `Transactions`, which contain messages that trigger state transitions. | ||
|
||
### State | ||
|
||
Here, we will define the types we need (excluding transaction types), as well as the stores in the multistore. | ||
|
||
Our voting module is very simple, we only need a single type: `Proposal`. `Proposals` are item to be voted upon. They can be submitted by any user. A deposit has to be provided. | ||
|
||
```go | ||
type Proposal struct { | ||
Title string // Title of the proposal | ||
Description string // Description of the proposal | ||
Submitter sdk.Address // Address of the submitter. Needed to refund deposit if proposal is accepted. | ||
SubmitBlock int64 // Block at which proposal is submitted. Also the block at which voting period begins. | ||
State string // State can be either "Open", "Accepted" or "Rejected" | ||
|
||
YesVotes int64 // Total number of Yes votes | ||
NoVotes int64 // Total number of No votes | ||
AbstainVotes int64 // Total number of Abstain votes | ||
} | ||
``` | ||
|
||
In terms of store, we will just create one [KVStore](#kvstore) in the multistore to store `Proposals`. We will also store the `Vote` (`Yes`, `No` or `Abstain`) chosen by each voter on each proposal. | ||
|
||
|
||
### Messages | ||
|
||
As a module developer, what you have to define are not `Transactions`, but `Messages`. Both transactions and messages exist in the Cosmos-SDK, but a transaction differs from a message in that a message is contained in a transaction. Transactions wrap around messages and add standard information like signatures and fees. As a module developer, you do not have to worry about transactions, only messages. | ||
|
||
Let us define the messages we need in order to modify the state. Based on the requirements above, we need to define two types of messages: | ||
|
||
- `SubmitProposalMsg`: to submit proposals | ||
- `VoteMsg`: to vote on proposals | ||
|
||
```go | ||
type SubmitProposalMsg struct { | ||
Title string // Title of the proposal | ||
Description string // Description of the proposal | ||
Deposit sdk.Coins // Deposit paid by submitter. Must be > MinDeposit to enter voting period | ||
Submitter sdk.Address // Address of the submitter | ||
} | ||
``` | ||
|
||
```go | ||
type VoteMsg struct { | ||
ProposalID int64 // ID of the proposal | ||
Option string // Option chosen by voter | ||
Voter sdk.Address // Address of the voter | ||
} | ||
``` |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
## Application initialization | ||
|
||
In the root of your fork of the SDK, create an `app` and `cmd` folder. In this folder, we will create the main file for our application, `app.go` and the repository to handle REST and CLI commands for our app. | ||
|
||
```bash | ||
mkdir app cmd | ||
mkdir -p cmd/simplegovcli cmd/simplegovd | ||
touch app/app.go cmd/simplegovcli/main.go cmd/simplegovd/main.go | ||
``` | ||
|
||
We will take care of these files later in the tutorial. The first step is to take care of our simple governance module. |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
## Makefile | ||
|
||
The [Makefile](https://en.wikipedia.org/wiki/Makefile) compiles the Go program by defining a set of rules with targets and recipes. We'll need to add our application commands to it: | ||
|
||
``` | ||
// Makefile | ||
build_examples: | ||
ifeq ($(OS),Windows_NT) | ||
... | ||
go build $(BUILD_FLAGS) -o build/simplegovd.exe ./examples/simpleGov/cmd/simplegovd | ||
go build $(BUILD_FLAGS) -o build/simplegovcli.exe ./examples/simpleGov/cmd/simplegovcli | ||
else | ||
... | ||
go build $(BUILD_FLAGS) -o build/simplegovd ./examples/simpleGov/cmd/simplegovd | ||
go build $(BUILD_FLAGS) -o build/simplegovcli ./examples/simpleGov/cmd/simplegovcli | ||
endif | ||
... | ||
install_examples: | ||
... | ||
go install $(BUILD_FLAGS) ./examples/simpleGov/cmd/simplegovd | ||
go install $(BUILD_FLAGS) ./examples/simpleGov/cmd/simplegovcli | ||
``` |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
##### Rest server | ||
|
||
**File: [`cmd/simplegovd/main.go`](https://github.com/cosmos/cosmos-sdk/blob/fedekunze/module_tutorial/examples/simpleGov/cmd/simplegovd/main.go)** | ||
|
||
The `simplegovd` command will run the daemon server as a background process. First, let us create some `utils` functions: | ||
|
||
```go | ||
// cmd/simplegovd/main.go | ||
// SimpleGovAppInit initial parameters | ||
var SimpleGovAppInit = server.AppInit{ | ||
AppGenState: SimpleGovAppGenState, | ||
AppGenTx: server.SimpleAppGenTx, | ||
} | ||
|
||
// SimpleGovAppGenState sets up the app_state and appends the simpleGov app state | ||
func SimpleGovAppGenState(cdc *wire.Codec, appGenTxs []json.RawMessage) (appState json.RawMessage, err error) { | ||
appState, err = server.SimpleAppGenState(cdc, appGenTxs) | ||
if err != nil { | ||
return | ||
} | ||
return | ||
} | ||
|
||
func newApp(logger log.Logger, db dbm.DB) abci.Application { | ||
return app.NewSimpleGovApp(logger, db) | ||
} | ||
|
||
func exportAppState(logger log.Logger, db dbm.DB) (json.RawMessage, error) { | ||
dapp := app.NewSimpleGovApp(logger, db) | ||
return dapp.ExportAppStateJSON() | ||
} | ||
``` | ||
|
||
Now, let us define the command for the daemon server within the `main()` function: | ||
|
||
```go | ||
// cmd/simplegovd/main.go | ||
func main() { | ||
cdc := app.MakeCodec() | ||
ctx := server.NewDefaultContext() | ||
|
||
rootCmd := &cobra.Command{ | ||
Use: "simplegovd", | ||
Short: "Simple Governance Daemon (server)", | ||
PersistentPreRunE: server.PersistentPreRunEFn(ctx), | ||
} | ||
|
||
server.AddCommands(ctx, cdc, rootCmd, SimpleGovAppInit, | ||
server.ConstructAppCreator(newApp, "simplegov"), | ||
server.ConstructAppExporter(exportAppState, "simplegov")) | ||
|
||
// prepare and add flags | ||
rootDir := os.ExpandEnv("$HOME/.simplegovd") | ||
executor := cli.PrepareBaseCmd(rootCmd, "BC", rootDir) | ||
executor.Execute() | ||
} | ||
``` |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
## Application structure | ||
|
||
Now, that we have built all the pieces we need, it is time to integrate them into the application. Let us exit the `/x` director go back at the root of the SDK directory. | ||
|
||
|
||
```bash | ||
// At root level of directory | ||
cd app | ||
``` | ||
|
||
We are ready to create our simple governance application! | ||
|
||
*Note: You can check the full file (with comments!) [here](link)* | ||
|
||
The `app.go` file is the main file that defines your application. In it, you will declare all the modules you need, their keepers, handlers, stores, etc. Let us take a look at each section of this file to see how the application is constructed. | ||
|
||
Secondly, we need to define the name of our application. | ||
|
||
```go | ||
const ( | ||
appName = "SimpleGovApp" | ||
) | ||
``` | ||
|
||
Then, let us define the structure of our application. | ||
|
||
```go | ||
// Extended ABCI application | ||
type SimpleGovApp struct { | ||
*bam.BaseApp | ||
cdc *wire.Codec | ||
|
||
// keys to access the substores | ||
capKeyMainStore *sdk.KVStoreKey | ||
capKeyAccountStore *sdk.KVStoreKey | ||
capKeyStakingStore *sdk.KVStoreKey | ||
capKeySimpleGovStore *sdk.KVStoreKey | ||
|
||
// keepers | ||
feeCollectionKeeper auth.FeeCollectionKeeper | ||
coinKeeper bank.Keeper | ||
stakeKeeper simplestake.Keeper | ||
simpleGovKeeper simpleGov.Keeper | ||
|
||
// Manage getting and setting accounts | ||
accountMapper auth.AccountMapper | ||
} | ||
``` | ||
|
||
- Each application builds on top of the `BaseApp` template, hence the pointer. | ||
- `cdc` is the codec used in our application. | ||
- Then come the keys to the stores we need in our application. For our simple governance app, we need 3 stores + the main store. | ||
- Then come the keepers and mappers. | ||
|
||
Let us do a quick reminder so that it is clear why we need these stores and keepers. Our application is primarily based on the `simple_governance` module. However, we have established in section [Keepers for our app](module-keeper.md) that our module needs access to two other modules: the `bank` module and the `stake` module. We also need the `auth` module for basic account functionalities. Finally, we need access to the main multistore to declare the stores of each of the module we use. |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
## Cast a vote to an existing proposal | ||
|
||
Let's cast a vote on the created proposal: | ||
|
||
```bash | ||
simplegovcli vote --proposal-id=1 --option="No" | ||
``` | ||
|
||
Get the value of the option from your casted vote : | ||
|
||
```bash | ||
simplegovcli proposal-vote 1 <your_address> | ||
``` | ||
|
||
You can also check all the casted votes of a proposal: | ||
|
||
```bash | ||
simplegovcli proposals-votes 1 | ||
``` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍👍👍