Cosmos Series-2. Cosmos SDK

1 Introduction

Last time I mentioned that Tendermint designed and encapsulated the network layer and consensus layer, and provided them to blockchain developers. In this case, only the application layer needs to be considered when developing a chain. However, many functions in the application layer can still be used in general, such as account management/Token transfer and other functions. Cosmos decomposes many common functions and implements them in a modular form, such as account management, community governance, staking, etc., which forms a new blockchain development framework Cosmos-SDK.

insert image description here

When developers use Cosmos-SDK for application development, they only need to implement special functions in their own business, and other functions can directly call the functional modules of Cosmos-SDK.

The following content consists of three parts:

  • A brief description of the Cosmos SDK
  • The logic of the community governance module
  • The main components of a Module

2. Cosmos SDK

The following are several directories extracted for easy understanding of the Cosmos framework (the code is all based on the Cosmos-SDK v0.33.2 version)

cosmos-sdk
├── baseapp         // Tendermint ABCI 接口进一步封装, 开发者基于它创建App
├── cmd             // 应用程序,hub的主程序gaia
├── store           // 实现了多种类型的存储,帮助App
└── x               // 插件, 模块化的通用功能
    ├── auth            // 账户管理和签名
    ├── bank            // Token和Token转账
    ├── gov             // 社区治理
    ├── ibc             // 跨链协议IBC
    ├── staking         // staking + slashing 完成了POS相关的实现,包括:绑定/解绑/通货膨胀/费用等
    └── slashing

2.1 baseapp

baseApp is the basic implementation of Cosmos-SDK's ABCI interface application. It routerbrings routing transactions to each corresponding module, and enriches the functions of App through modules.
Review the ABCI interface provided by Tendermint:

type Application interface {
	// Info/Query Connection
	Info(RequestInfo) ResponseInfo                // Return application info
	SetOption(RequestSetOption) ResponseSetOption // Set application option
	Query(RequestQuery) ResponseQuery             // Query for state

	// Mempool Connection
	CheckTx(RequestCheckTx) ResponseCheckTx // Validate a tx for the mempool

	// Consensus Connection
	InitChain(RequestInitChain) ResponseInitChain    // Initialize blockchain with validators and other info from TendermintCore
	BeginBlock(RequestBeginBlock) ResponseBeginBlock // Signals the beginning of a block
	DeliverTx(RequestDeliverTx) ResponseDeliverTx    // Deliver a tx for full processing
	EndBlock(RequestEndBlock) ResponseEndBlock       // Signals the end of a block, returns changes to the validator set
	Commit() ResponseCommit                          // Commit the state and return the application Merkle root hash
}

Let's take a look at the structure of BaseApp

// Cosmos-SDK/baseapp/baseapp.go
// BaseApp reflects the ABCI application implementation.
type BaseApp struct {
	name        string               // application name from abci.Info
	db          dbm.DB               // common DB backend
	cms         sdk.CommitMultiStore // Main (uncached) state
	router      Router               // handle any kind of message
	queryRouter QueryRouter          // router for redirecting query calls
	txDecoder   sdk.TxDecoder        // unmarshal []byte into sdk.Tx

	// set upon LoadVersion or LoadLatestVersion.
	baseKey *sdk.KVStoreKey // Main KVStore in cms

	anteHandler    sdk.AnteHandler  // ante handler for fee and auth
	initChainer    sdk.InitChainer  // initialize state with validators and state blob
	beginBlocker   sdk.BeginBlocker // logic to run before any txs
	endBlocker     sdk.EndBlocker   // logic to run after all txs, and to determine valset changes
    ...
}

On the basis of encapsulating the ABCI interface, baseApp reserves several processing functions to be implemented by App and called when appropriate.

The following is a simple transaction processing flow of an App developed based on CosmosSDK:

    1. Decode transactions received from the Tendermint consensus engine (DeliverTx)
    1. Extract messages from transactions and do basic checks
    1. Route each message to its own module for processing
    1. commit status change
// 处理交易的流程
func (app *BaseApp) DeliverTx(txBytes []byte) (res abci.ResponseDeliverTx) {
    var result sdk.Result
	tx, err := app.txDecoder(txBytes)
	
	...
    result = app.runTx(runTxModeDeliver, txBytes, tx)
    ...
}

func (app *BaseApp) runTx(mode runTxMode, txBytes []byte, tx sdk.Tx) (result sdk.Result) {
	...
	ctx := app.getContextForTx(mode, txBytes)
	ms := ctx.MultiStore()
    ...
	runMsgCtx, msCache := app.cacheTxContext(ctx, txBytes)
	result = app.runMsgs(runMsgCtx, msgs, mode)
	...
}

func (app *BaseApp) runMsgs(ctx sdk.Context, msgs []sdk.Msg, mode runTxMode) (result sdk.Result) {
	...
	for msgIdx, msg := range msgs {
		// match message route
		msgRoute := msg.Route()
		handler := app.router.Route(msgRoute)
		if handler == nil {
			return sdk.ErrUnknownRequest("Unrecognized Msg type: " + msgRoute).Result()
		}
		if mode != runTxModeCheck {
			msgResult = handler(ctx, msg)
		}
		...
	}
    ...
}

The following is the implementation of Router, which is a map mapping of a path string and the corresponding processing function.

type Router interface {
	AddRoute(r string, h sdk.Handler) (rtr Router)
	Route(path string) (h sdk.Handler)
}

type router struct {
	routes map[string]sdk.Handler
}

func NewRouter() *router { // nolint: golint
	return &router{
		routes: make(map[string]sdk.Handler),
	}
}

func (rtr *router) AddRoute(path string, h sdk.Handler) Router {
    ...
	rtr.routes[path] = h
	...
}

func (rtr *router) Route(path string) sdk.Handler {
	return rtr.routes[path]
}

2.2 store

Cosmos SDK provides a Multistore persistent storage. It allows developers to create any number of KVStores to separate the states of different modules. These KVStores can only store []bytevalues ​​of the type, so all custom structures must be encoded using Amino before being stored.

Amino 是Cosmos团队基于Google的Protobuffer开发的新的编码格式.

2.3 module

The power of the Cosmos SDK lies in its modularity. SDK applications are built by aggregating a set of interoperable modules. Each module defines its own state and message/transaction processing flow, and the SDK is responsible for forwarding the message to the corresponding module.

                                      +
                                      |
                                      |  Transaction relayed from Tendermint
                                      |  via DeliverTx
                                      |
                                      |
                +---------------------v--------------------------+
                |                 APPLICATION                    |
                |                                                |
                |     Using baseapp's methods: Decode the Tx,    |
                |     extract and route the message(s)           |
                |                                                |
                +---------------------+--------------------------+
                                      |
                                      |
                                      |
                                      +---------------------------+
                                                                  |
                                                                  |
                                                                  |
                                                                  |  Message routed to the correct
                                                                  |  module to be processed
                                                                  |
                                                                  |
+----------------+  +---------------+  +----------------+  +------v----------+
|                |  |               |  |                |  |                 |
|  AUTH MODULE   |  |  BANK MODULE  |  | STAKING MODULE |  |   GOV MODULE    |
|                |  |               |  |                |  |                 |
|                |  |               |  |                |  | Handles message,|
|                |  |               |  |                |  | Updates state   |
|                |  |               |  |                |  |                 |
+----------------+  +---------------+  +----------------+  +------+----------+
                                                                  |
                                                                  |
                                                                  |
                                                                  |
                                       +--------------------------+
                                       |
                                       | Return result to Tendermint
                                       | (0=Ok, 1=Err)
                                       v

3. Governance module

Cosmos has a built-in governance system that allows holders of staked tokens to vote on proposals. The system now supports 3 proposal types:

  • Text Proposals: This is the most basic type of proposal and is usually used to get people's views on a network governance opinion.
  • Parameter Proposals: This kind of proposal is usually used to change the setting of network parameters.
  • Software Upgrade Proposal: This proposal is used to upgrade the Hub's software.

Any staked token holder will be able to submit a proposal. In order for a proposal to be approved for public voting, the proposer must pay a certain amount of deposit, and the deposit value must be greater than the value set by the minDeposit parameter. The deposit of the proposal does not need to be paid in full by the proposer. If the early proposers deliver insufficient deposits, the proposal enters the deposit_period state. Thereafter, any token holder can increase their deposit to the proposal through depositTx transactions.
When the deposit reaches minDeposit, the proposal enters a voting_period of 2 weeks. Token holders of any staking state can participate in voting on this proposal. The options for voting are Yes, No, NoWithVeto (negation of exercising the veto right) and Abstain (abstaining from voting). The weight of the vote depends on the number of tokens pledged by the voter. If token holders do not vote, delegators will inherit the voting options of their delegated validators. Of course, the delegator can also vote differently from the delegated verifier.
When the voting period is over, NoWithVeto (excluding Abstain votes) proposals with more than 50% (excluding Abstain votes) and less than 33.33% of the Yes votes will be accepted.
If the proposal is accepted, the corresponding proposal content needs to be implemented.

The above is the business logic of the governance module, and the following describes the main components of a module through code:

x
└── gov
     ├── client                     // 接口
     │   ├── cli                    // 命令行客户端接口
     │   │    ├── query.go          // 发起Query获取信息
     │   │    └── tx.go             // 构建Tx发送相应的Msg
     │   ├── rest                   // reset 请求接口
     │   │    └── rest.go           // 包含了Query和Tx所有消息处理
     │   └── module_client.go       // ModuleClient, 提供命令和路由
     ├── codec.go                   // 模块内的编解码
     ├── keeper.go                  // 存储,并与其他模块的keeper交互
     ├── msg.go                     // 模块内用到的Msg,也就是Tx
     ├── handler.go                 // 处理Msg的函数
     └── querier.go                 // 处理Query的函数
3.0 types and params

Data Structures in the Cosmos Governance System

type ProposalKind byte
const (
	ProposalTypeNil             ProposalKind = 0x00
	ProposalTypeText            ProposalKind = 0x01
	ProposalTypeParameterChange ProposalKind = 0x02
	ProposalTypeSoftwareUpgrade ProposalKind = 0x03
)

type TextProposal struct {
	ProposalID   uint64       `json:"proposal_id"`   //  ID of the proposal
	Title        string       `json:"title"`         //  Title of the proposal
	Description  string       `json:"description"`   //  Description of the proposal
	ProposalType ProposalKind `json:"proposal_type"` //  Type of proposal. Initial set {PlainTextProposal, SoftwareUpgradeProposal}

	Status           ProposalStatus `json:"proposal_status"`    //  Status of the Proposal {Pending, Active, Passed, Rejected}
	FinalTallyResult TallyResult    `json:"final_tally_result"` //  Result of Tallys

	SubmitTime     time.Time `json:"submit_time"`      //  Time of the block where TxGovSubmitProposal was included
	DepositEndTime time.Time `json:"deposit_end_time"` // Time that the Proposal would expire if deposit amount isn't met
	TotalDeposit   sdk.Coins `json:"total_deposit"`    //  Current deposit on this proposal. Initial value is set at InitialDeposit

	VotingStartTime time.Time `json:"voting_start_time"` //  Time of the block where MinDeposit was reached. -1 if MinDeposit is not reached
	VotingEndTime   time.Time `json:"voting_end_time"`   // Time that the VotingPeriod for this proposal will end and votes will be tallied
}

type ProposalStatus byte
const (
	StatusNil           ProposalStatus = 0x00
	StatusDepositPeriod ProposalStatus = 0x01
	StatusVotingPeriod  ProposalStatus = 0x02
	StatusPassed        ProposalStatus = 0x03
	StatusRejected      ProposalStatus = 0x04
)

// Vote
type Vote struct {
	Voter      sdk.AccAddress `json:"voter"`       //  address of the voter
	ProposalID uint64         `json:"proposal_id"` //  proposalID of the proposal
	Option     VoteOption     `json:"option"`      //  option from OptionSet chosen by the voter
}

// Deposit
type Deposit struct {
	Depositor  sdk.AccAddress `json:"depositor"`   //  Address of the depositor
	ProposalID uint64         `json:"proposal_id"` //  proposalID of the proposal
	Amount     sdk.Coins      `json:"amount"`      //  Deposit amount
}

type VoteOption byte
const (
	OptionEmpty      VoteOption = 0x00
	OptionYes        VoteOption = 0x01
	OptionAbstain    VoteOption = 0x02
	OptionNo         VoteOption = 0x03
	OptionNoWithVeto VoteOption = 0x04
)

Some parameters in the governance system

type DepositParams struct {
	MinDeposit       sdk.Coins     `json:"min_deposit"`        //  Minimum deposit for a proposal to enter voting period.
	MaxDepositPeriod time.Duration `json:"max_deposit_period"` //  Maximum period for Atom holders to deposit on a proposal. Initial value: 2 months
}
type TallyParams struct {
	Quorum    sdk.Dec `json:"quorum"`    //  Minimum percentage of total stake needed to vote for a result to be considered valid
	Threshold sdk.Dec `json:"threshold"` //  Minimum propotion of Yes votes for proposal to pass. Initial value: 0.5
	Veto      sdk.Dec `json:"veto"`      //  Minimum value of Veto votes to Total votes ratio for proposal to be vetoed. Initial value: 1/3
}
type VotingParams struct {
	VotingPeriod time.Duration `json:"voting_period"` //  Length of the voting period.
}
3.1 keeper

Keeper is used to handle the interaction between the module and the storage, including most of the core functions of the module.
In the above logic, the keeper needs to handle the storage/query/deletion of Proposal/Deposit/Vote.

The following extracts a part of the code for analysis

// Cosmos-SDK/x/gov/keeper.go

type Keeper struct {
	// The reference to the CoinKeeper to modify balances
	ck BankKeeper

	// The (unexposed) keys used to access the stores from the Context.
	storeKey sdk.StoreKey

	// The codec codec for binary encoding/decoding.
	cdc *codec.Codec
}

gov's keeper structure

  • BankKeeper This is the keeper reference of the bank module, including the methods it can call the bank module. For example, it processes the deposit submitted by the validator during the Deposit phase of the proposal
  • sdk.StoreKey This is a storage key of the App layer KVStore database, which is used to operate the stored Value in the module
  • *codec.Codec This is a codec pointer, providing Amino codec interface
// Creates a NewProposal
func (keeper Keeper) NewTextProposal(ctx sdk.Context, title string, description string, proposalType ProposalKind) Proposal {
	proposalID, err := keeper.getNewProposalID(ctx)
	if err != nil {
		return nil
	}
	var proposal Proposal = &TextProposal{
		ProposalID:       proposalID,
		Title:            title,
		Description:      description,
		ProposalType:     proposalType,
		Status:           StatusDepositPeriod,
		FinalTallyResult: EmptyTallyResult(),
		TotalDeposit:     sdk.Coins{},
		SubmitTime:       ctx.BlockHeader().Time,
	}

	depositPeriod := keeper.GetDepositParams(ctx).MaxDepositPeriod
	proposal.SetDepositEndTime(proposal.GetSubmitTime().Add(depositPeriod))

	keeper.SetProposal(ctx, proposal)
	keeper.InsertInactiveProposalQueue(ctx, proposal.GetDepositEndTime(), proposalID)
	return proposal
}

// Get Proposal from store by ProposalID
func (keeper Keeper) GetProposal(ctx sdk.Context, proposalID uint64) Proposal {
	store := ctx.KVStore(keeper.storeKey)
	bz := store.Get(KeyProposal(proposalID))
	if bz == nil {
		return nil
	}
	var proposal Proposal
	keeper.cdc.MustUnmarshalBinaryLengthPrefixed(bz, &proposal)
	return proposal
}

// Implements sdk.AccountKeeper.
func (keeper Keeper) DeleteProposal(ctx sdk.Context, proposalID uint64) {
	store := ctx.KVStore(keeper.storeKey)
	proposal := keeper.GetProposal(ctx, proposalID)
	keeper.RemoveFromInactiveProposalQueue(ctx, proposal.GetDepositEndTime(), proposalID)
	keeper.RemoveFromActiveProposalQueue(ctx, proposal.GetVotingEndTime(), proposalID)
	store.Delete(KeyProposal(proposalID))
}

The above is about the new/query/delete interface of Proposal, and the methods related to Deposit and Vote are not shown in detail.

3.2 msg and handler

msg is the operation initiated by the transaction supported in the module.
In the above governance logic, the Msg that needs to be defined includes (SubmitProposal/Deposit)

  • SubmitProposal : Submit a proposal
  • Deposit : Provide a deposit for a proposal
  • Vote : vote for a proposal
// Cosmos-SDK/x/gov/msg.go
// MsgDeposit
type MsgDeposit struct {
	ProposalID uint64         `json:"proposal_id"` // ID of the proposal
	Depositor  sdk.AccAddress `json:"depositor"`   // Address of the depositor
	Amount     sdk.Coins      `json:"amount"`      // Coins to add to the proposal's deposit
}

func NewMsgDeposit(depositor sdk.AccAddress, proposalID uint64, amount sdk.Coins) MsgDeposit {
	return MsgDeposit{
		ProposalID: proposalID,
		Depositor:  depositor,
		Amount:     amount,
	}
}

// Implements Msg.
func (msg MsgDeposit) Route() string { return RouterKey }
func (msg MsgDeposit) Type() string  { return TypeMsgDeposit }

func (msg MsgDeposit) ValidateBasic() sdk.Error {
	if msg.Depositor.Empty() {
		return sdk.ErrInvalidAddress(msg.Depositor.String())
	}
	if !msg.Amount.IsValid() {
		return sdk.ErrInvalidCoins(msg.Amount.String())
	}
	if msg.Amount.IsAnyNegative() {
		return sdk.ErrInvalidCoins(msg.Amount.String())
	}
	if msg.ProposalID < 0 {
		return ErrUnknownProposal(DefaultCodespace, msg.ProposalID)
	}
	return nil
}

func (msg MsgDeposit) String() string {
	return fmt.Sprintf("MsgDeposit{%s=>%v: %v}", msg.Depositor, msg.ProposalID, msg.Amount)
}

func (msg MsgDeposit) GetSignBytes() []byte {
	bz := msgCdc.MustMarshalJSON(msg)
	return sdk.MustSortJSON(bz)
}

func (msg MsgDeposit) GetSigners() []sdk.AccAddress {
	return []sdk.AccAddress{msg.Depositor}
}

msg only defines the message type and implements some interfaces of the message. When the module receives the corresponding message, the processing function is implemented in the handler.

// Cosmos-SDK/x/gov/handler.go
func NewHandler(keeper Keeper) sdk.Handler {
	return func(ctx sdk.Context, msg sdk.Msg) sdk.Result {
		switch msg := msg.(type) {
		case MsgDeposit:
			return handleMsgDeposit(ctx, keeper, msg)
		case MsgSubmitProposal:
			return handleMsgSubmitProposal(ctx, keeper, msg)
		case MsgVote:
			return handleMsgVote(ctx, keeper, msg)
		default:
			errMsg := fmt.Sprintf("Unrecognized gov msg type: %T", msg)
			return sdk.ErrUnknownRequest(errMsg).Result()
		}
	}
}

func handleMsgDeposit(ctx sdk.Context, keeper Keeper, msg MsgDeposit) sdk.Result {
	err, votingStarted := keeper.AddDeposit(ctx, msg.ProposalID, msg.Depositor, msg.Amount)
	if err != nil {
		return err.Result()
	}

	proposalIDStr := fmt.Sprintf("%d", msg.ProposalID)
	resTags := sdk.NewTags(
		tags.Depositor, []byte(msg.Depositor.String()),
		tags.ProposalID, proposalIDStr,
	)

	if votingStarted {
		resTags = resTags.AppendTag(tags.VotingPeriodStart, proposalIDStr)
	}

	return sdk.Result{
		Tags: resTags,
	}
}
3.3 codec

codec is mainly to register the custom types in the module to Amino, so that they can support codec.

var msgCdc = codec.New()

// Register concrete types on codec codec
func RegisterCodec(cdc *codec.Codec) {
	cdc.RegisterConcrete(MsgSubmitProposal{}, "cosmos-sdk/MsgSubmitProposal", nil)
	cdc.RegisterConcrete(MsgDeposit{}, "cosmos-sdk/MsgDeposit", nil)
	cdc.RegisterConcrete(MsgVote{}, "cosmos-sdk/MsgVote", nil)

	cdc.RegisterInterface((*Proposal)(nil), nil)
	cdc.RegisterConcrete(&TextProposal{}, "gov/TextProposal", nil)
}

func init() {
	RegisterCodec(msgCdc)
}
3.4 querier

What is defined in Querier is the queryable interface provided by the module to the outside.
In the above logic, queryer includes

  • Query all proposals that currently exist
  • Query the deposit amount of the proposal
  • Query the number of votes for a proposal
  • etc.

The ABCI interface implemented in baseapp Queryis used to perform queries, and different query requests are queryRouterrouted to corresponding modules according to the routing principle, which is the same as transaction routing.

// Cosmos-SDK/baseapp/baseapp.go
func (app *BaseApp) Query(req abci.RequestQuery) (res abci.ResponseQuery) {
	path := splitPath(req.Path)
	if len(path) == 0 {
		msg := "no query path provided"
		return sdk.ErrUnknownRequest(msg).QueryResult()
	}

	switch path[0] {
	// "/app" prefix for special application queries
	case "app":
		return handleQueryApp(app, path, req)

	case "store":
		return handleQueryStore(app, path, req)

	case "p2p":
		return handleQueryP2P(app, path, req)

	case "custom":
		return handleQueryCustom(app, path, req)
	}

	msg := "unknown query path"
	return sdk.ErrUnknownRequest(msg).QueryResult()
}

func handleQueryCustom(app *BaseApp, path []string, req abci.RequestQuery) (res abci.ResponseQuery) {
	// path[0] should be "custom" because "/custom" prefix is required for keeper
	// queries.
	
	querier := app.queryRouter.Route(path[1])
	if querier == nil {
		return sdk.ErrUnknownRequest(fmt.Sprintf("no custom querier found for route %s", path[1])).QueryResult()
	}

	resBytes, err := querier(ctx, path[2:], req)
	if err != nil {
		return abci.ResponseQuery{
			Code:      uint32(err.Code()),
			Codespace: string(err.Codespace()),
			Log:       err.ABCILog(),
		}
	}
	...
}
3.5 client

The above querier and handler have completed the processing process of requests and transactions in the management module, then the client module is to provide users with an interface to initiate requests and transactions.

The cli directory is an interface provided to App command line operations:

// Cosmos-SDK/x/gov/client/cli/query.go
// GetCmdQueryDeposit implements the query proposal deposit command.
func GetCmdQueryDeposit(queryRoute string, cdc *codec.Codec) *cobra.Command {
	return &cobra.Command{
		Use:   "deposit [proposal-id] [depositer-addr]",
		Args:  cobra.ExactArgs(2),
		Short: "Query details of a deposit",
		Long: strings.TrimSpace(`
Query details for a single proposal deposit on a proposal by its identifier.

Example:
$ gaiacli query gov deposit 1 cosmos1skjwj5whet0lpe65qaq4rpq03hjxlwd9nf39lk
`),
		RunE: func(cmd *cobra.Command, args []string) error {
			cliCtx := context.NewCLIContext().WithCodec(cdc)

			// validate that the proposal id is a uint
			proposalID, err := strconv.ParseUint(args[0], 10, 64)
			...
			_, err = gcutils.QueryProposalByID(proposalID, cliCtx, cdc, queryRoute)
			if err != nil {
				return fmt.Errorf("Failed to fetch proposal-id %d: %s", proposalID, err)
			}

			depositorAddr, err := sdk.AccAddressFromBech32(args[1])
			if err != nil {
				return err
			}

			params := gov.NewQueryDepositParams(proposalID, depositorAddr)
			bz, err := cdc.MarshalJSON(params)
			if err != nil {
				return err
			}

			res, err := cliCtx.QueryWithData(fmt.Sprintf("custom/%s/deposit", queryRoute), bz)
			if err != nil {
				return err
			}
            ...
		},
	}
}

// Cosmos-SDK/x/gov/client/cli/tx.go
// GetCmdDeposit implements depositing tokens for an active proposal.
func GetCmdDeposit(queryRoute string, cdc *codec.Codec) *cobra.Command {
	return &cobra.Command{
		Use:   "deposit [proposal-id] [deposit]",
		Args:  cobra.ExactArgs(2),
		Short: "Deposit tokens for activing proposal",
		Long: strings.TrimSpace(`
Submit a deposit for an acive proposal. You can find the proposal-id by running gaiacli query gov proposals:

$ gaiacli tx gov deposit 1 10stake --from mykey
`),
		RunE: func(cmd *cobra.Command, args []string) error {
			txBldr := authtxb.NewTxBuilderFromCLI().WithTxEncoder(utils.GetTxEncoder(cdc))
			cliCtx := context.NewCLIContext().
				WithCodec(cdc).
				WithAccountDecoder(cdc)

			// validate that the proposal id is a uint
			proposalID, err := strconv.ParseUint(args[0], 10, 64)
			if err != nil {
				return fmt.Errorf("proposal-id %s not a valid uint, please input a valid proposal-id", args[0])
			}
            ...
            from := cliCtx.GetFromAddress()
            ...
			// Get amount of coins
			amount, err := sdk.ParseCoins(args[1])
			...

			msg := gov.NewMsgDeposit(from, proposalID, amount)
			err = msg.ValidateBasic()
			if err != nil {
				return err
			}

			return utils.GenerateOrBroadcastMsgs(cliCtx, txBldr, []sdk.Msg{msg}, false)
		},
	}
}

The rest directory is the provided REST interface

Register all provided request paths and corresponding execution functions

// Cosmos-SDK/x/gov/client/rest/rest.go
func RegisterRoutes(cliCtx context.CLIContext, r *mux.Router, cdc *codec.Codec) {
	r.HandleFunc("/gov/proposals", postProposalHandlerFn(cdc, cliCtx)).Methods("POST")
	r.HandleFunc(fmt.Sprintf("/gov/proposals/{%s}/deposits", RestProposalID), depositHandlerFn(cdc, cliCtx)).Methods("POST")
	r.HandleFunc(fmt.Sprintf("/gov/proposals/{%s}/votes", RestProposalID), voteHandlerFn(cdc, cliCtx)).Methods("POST")

	r.HandleFunc(
		fmt.Sprintf("/gov/parameters/{%s}", RestParamsType),
		queryParamsHandlerFn(cdc, cliCtx),
	).Methods("GET")

	r.HandleFunc("/gov/proposals", queryProposalsWithParameterFn(cdc, cliCtx)).Methods("GET")
	r.HandleFunc(fmt.Sprintf("/gov/proposals/{%s}", RestProposalID), queryProposalHandlerFn(cdc, cliCtx)).Methods("GET")
	r.HandleFunc(
		fmt.Sprintf("/gov/proposals/{%s}/proposer", RestProposalID),
		queryProposerHandlerFn(cdc, cliCtx),
	).Methods("GET")
	r.HandleFunc(fmt.Sprintf("/gov/proposals/{%s}/deposits", RestProposalID), queryDepositsHandlerFn(cdc, cliCtx)).Methods("GET")
	r.HandleFunc(fmt.Sprintf("/gov/proposals/{%s}/deposits/{%s}", RestProposalID, RestDepositor), queryDepositHandlerFn(cdc, cliCtx)).Methods("GET")
	r.HandleFunc(fmt.Sprintf("/gov/proposals/{%s}/tally", RestProposalID), queryTallyOnProposalHandlerFn(cdc, cliCtx)).Methods("GET")
	r.HandleFunc(fmt.Sprintf("/gov/proposals/{%s}/votes", RestProposalID), queryVotesOnProposalHandlerFn(cdc, cliCtx)).Methods("GET")
	r.HandleFunc(fmt.Sprintf("/gov/proposals/{%s}/votes/{%s}", RestProposalID, RestVoter), queryVoteHandlerFn(cdc, cliCtx)).Methods("GET")
}

func depositHandlerFn(cdc *codec.Codec, cliCtx context.CLIContext) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		vars := mux.Vars(r)
		strProposalID := vars[RestProposalID]
        ...

		var req DepositReq
		if !rest.ReadRESTReq(w, r, cdc, &req) {
			return
		}

		req.BaseReq = req.BaseReq.Sanitize()
		if !req.BaseReq.ValidateBasic(w) {
			return
		}

		// create the message
		msg := gov.NewMsgDeposit(req.Depositor, proposalID, req.Amount)
		if err := msg.ValidateBasic(); err != nil {
			rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error())
			return
		}

		clientrest.WriteGenerateStdTxResponse(w, cdc, cliCtx, req.BaseReq, []sdk.Msg{msg})
	}
}

module client exports all the client operation interfaces of the module, and finally binds them to the subcommands of the main program.

// Cosmos-SDK/x/gov/client/module_client.go
type ModuleClient struct {
	storeKey string
	cdc      *amino.Codec
}

func NewModuleClient(storeKey string, cdc *amino.Codec) ModuleClient {
	return ModuleClient{storeKey, cdc}
}

// GetQueryCmd returns the cli query commands for this module
func (mc ModuleClient) GetQueryCmd() *cobra.Command {
	// Group gov queries under a subcommand
	govQueryCmd := &cobra.Command{
		Use:   gov.ModuleName,
		Short: "Querying commands for the governance module",
	}

	govQueryCmd.AddCommand(client.GetCommands(
		govCli.GetCmdQueryProposal(mc.storeKey, mc.cdc),
		govCli.GetCmdQueryProposals(mc.storeKey, mc.cdc),
		govCli.GetCmdQueryVote(mc.storeKey, mc.cdc),
		govCli.GetCmdQueryVotes(mc.storeKey, mc.cdc),
		govCli.GetCmdQueryParam(mc.storeKey, mc.cdc),
		govCli.GetCmdQueryParams(mc.storeKey, mc.cdc),
		govCli.GetCmdQueryProposer(mc.storeKey, mc.cdc),
		govCli.GetCmdQueryDeposit(mc.storeKey, mc.cdc),
		govCli.GetCmdQueryDeposits(mc.storeKey, mc.cdc),
		govCli.GetCmdQueryTally(mc.storeKey, mc.cdc))...)

	return govQueryCmd
}

// GetTxCmd returns the transaction commands for this module
func (mc ModuleClient) GetTxCmd() *cobra.Command {
	govTxCmd := &cobra.Command{
		Use:   gov.ModuleName,
		Short: "Governance transactions subcommands",
	}

	govTxCmd.AddCommand(client.PostCommands(
		govCli.GetCmdDeposit(mc.storeKey, mc.cdc),
		govCli.GetCmdVote(mc.storeKey, mc.cdc),
		govCli.GetCmdSubmitProposal(mc.cdc),
	)...)

	return govTxCmd
}

4. Summary

After analyzing the code of the governance module, this article sorts out the Cosmos module development framework, which will be of great help to students who want to write Cosmos App and plug-ins.

Guess you like

Origin blog.csdn.net/mynameislu/article/details/99729066