1. 前言

上次讲到 Tendermint 将网络层和共识层设计好并封装起来,提供给区块链开发者使用,在这种情况下开发一个链时只需要考虑应用层。 不过应用层中有很多功能还是可以通用的,比如账户管理/Token转账等功能。Cosmos把很多通用功能分解出来,以模块化的形式加以实现,例如:账户管理,社区治理,Staking等等,这就组成了一套新的区块链开发框架Cosmos-SDK。

开发者使用Cosmos-SDK进行应用开发时,只需要实现自身业务中特殊的功能,其他功能可以直接调用Cosmos-SDK的功能模块。

下面的内容包括三部分:

  • Cosmos SDK 的简单描述
  • 社区治理模块的逻辑
  • 一个Module的主要构成

2. Cosmos SDK

下面是为了易于理解Cosmos的框架抽取出来的几个目录(代码全部基于 Cosmos-SDK v0.33.2版本)

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 是Cosmos-SDK 的ABCI接口应用的基础实现,它自带router来路由交易到各个对应的模块,通过模块丰富App的功能。
回顾一下Tendermint提供的ABCI接口:

type Application interface {// Info/Query ConnectionInfo(RequestInfo) ResponseInfo                // Return application infoSetOption(RequestSetOption) ResponseSetOption // Set application optionQuery(RequestQuery) ResponseQuery             // Query for state// Mempool ConnectionCheckTx(RequestCheckTx) ResponseCheckTx // Validate a tx for the mempool// Consensus ConnectionInitChain(RequestInitChain) ResponseInitChain    // Initialize blockchain with validators and other info from TendermintCoreBeginBlock(RequestBeginBlock) ResponseBeginBlock // Signals the beginning of a blockDeliverTx(RequestDeliverTx) ResponseDeliverTx    // Deliver a tx for full processingEndBlock(RequestEndBlock) ResponseEndBlock       // Signals the end of a block, returns changes to the validator setCommit() ResponseCommit                          // Commit the state and return the application Merkle root hash
}

下面看一下BaseApp的结构体

// Cosmos-SDK/baseapp/baseapp.go
// BaseApp reflects the ABCI application implementation.
type BaseApp struct {name        string               // application name from abci.Infodb          dbm.DB               // common DB backendcms         sdk.CommitMultiStore // Main (uncached) staterouter      Router               // handle any kind of messagequeryRouter QueryRouter          // router for redirecting query callstxDecoder   sdk.TxDecoder        // unmarshal []byte into sdk.Tx// set upon LoadVersion or LoadLatestVersion.baseKey *sdk.KVStoreKey // Main KVStore in cmsanteHandler    sdk.AnteHandler  // ante handler for fee and authinitChainer    sdk.InitChainer  // initialize state with validators and state blobbeginBlocker   sdk.BeginBlocker // logic to run before any txsendBlocker     sdk.EndBlocker   // logic to run after all txs, and to determine valset changes...
}

baseApp 在封装了ABCI接口的基础上,预留了几个处理函数由App实现并在适当的时候被调用。

下面是一个基于CosmosSDK开发的App简单的交易处理流程:

    1. 将从Tendermint共识引擎(DeliverTx)收到的交易进行解码
    1. 从交易中提取messages 并做基本的检查
    1. 将每个message路由到各自的模块进行处理
    1. 提交状态变化
// 处理交易的流程
func (app *BaseApp) DeliverTx(txBytes []byte) (res abci.ResponseDeliverTx) {var result sdk.Resulttx, 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 routemsgRoute := 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)}...}...
}

下面是Router的实现,是一个 path string 和对应的处理函数的map映射.

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: golintreturn &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 提供了一个Multistore 持久化存储。它允许开发者创建任意个KVStores, 用来分别不同模块的state。这些KVStores只能存储[]byte类型的值,因此所有自定义的结构都要在存储之前使用Amino进行编码。

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

2.3 module

Cosmos SDK的强大之处就在于它的模块化。SDK应用程序是通过聚合一组可互操作的模块来构建的。每个模块定义各自的state和message/transaction 处理流程,SDK负责将message转发到对应的模块。

                                      +||  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. 治理模块

Cosmos 有一个内建的治理系统,该系统允许抵押通证的持有人参与提案投票。系统现在支持3种提案类型:

  • Text Proposals: 这是最基本的一种提案类型,通常用于获得大家对某个网络治理意见的观点。
  • Parameter Proposals: 这种提案通常用于改变网络参数的设定。
  • Software Upgrade Proposal: 这个提案用于升级Hub的软件。

任何质押通证的持有人都能够提交一个提案。为了让一个提案获准公开投票,提议人必须要缴纳一定量的押金 deposit,且押金值必须大于 minDeposit 参数设定值。 提案的押金不需要提案人一次全部交付。如果早期提案人交付的 deposit 不足,那么提案进入 deposit_period 状态。 此后,任何通证持有人可以通过 depositTx 交易增加对提案的押金。
当deposit 达到 minDeposit,提案进入2周的 voting_period 。 任何质押状态的通证持有人都可以参与对这个提案的投票。投票的选项有Yes, No, NoWithVeto(行使否决权的否定) 和 Abstain(弃权)。投票的权重取决于投票人所质押的通证数量。如果通证持有人不投票,那么委托人将继承其委托的验证人的投票选项。当然,委托人也可以自己投出与所委托验证人不同的票。
当投票期结束后,获得50%(不包括投Abstain 票)以上 Yes 投票权重且少于33.33% 的NoWithVeto(不包括投Abstain 票)提案将被接受。
如果提案被接受,相应的提案内容需要实现。

上面是治理模块的业务逻辑,下面通过代码来讲述一个模块的主要构成:

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

Cosmos治理系统中的数据结构

type ProposalKind byte
const (ProposalTypeNil             ProposalKind = 0x00ProposalTypeText            ProposalKind = 0x01ProposalTypeParameterChange ProposalKind = 0x02ProposalTypeSoftwareUpgrade ProposalKind = 0x03
)type TextProposal struct {ProposalID   uint64       `json:"proposal_id"`   //  ID of the proposalTitle        string       `json:"title"`         //  Title of the proposalDescription  string       `json:"description"`   //  Description of the proposalProposalType 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 TallysSubmitTime     time.Time `json:"submit_time"`      //  Time of the block where TxGovSubmitProposal was includedDepositEndTime time.Time `json:"deposit_end_time"` // Time that the Proposal would expire if deposit amount isn't metTotalDeposit   sdk.Coins `json:"total_deposit"`    //  Current deposit on this proposal. Initial value is set at InitialDepositVotingStartTime time.Time `json:"voting_start_time"` //  Time of the block where MinDeposit was reached. -1 if MinDeposit is not reachedVotingEndTime   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 = 0x00StatusDepositPeriod ProposalStatus = 0x01StatusVotingPeriod  ProposalStatus = 0x02StatusPassed        ProposalStatus = 0x03StatusRejected      ProposalStatus = 0x04
)// Vote
type Vote struct {Voter      sdk.AccAddress `json:"voter"`       //  address of the voterProposalID uint64         `json:"proposal_id"` //  proposalID of the proposalOption     VoteOption     `json:"option"`      //  option from OptionSet chosen by the voter
}// Deposit
type Deposit struct {Depositor  sdk.AccAddress `json:"depositor"`   //  Address of the depositorProposalID uint64         `json:"proposal_id"` //  proposalID of the proposalAmount     sdk.Coins      `json:"amount"`      //  Deposit amount
}type VoteOption byte
const (OptionEmpty      VoteOption = 0x00OptionYes        VoteOption = 0x01OptionAbstain    VoteOption = 0x02OptionNo         VoteOption = 0x03OptionNoWithVeto VoteOption = 0x04
)

治理系统中的一些参数

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 validThreshold sdk.Dec `json:"threshold"` //  Minimum propotion of Yes votes for proposal to pass. Initial value: 0.5Veto      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用来处理模块与存储的交互,包含模块大部分核心功能。
在上面的逻辑中,keeper需要处理 Proposal/Deposit/Vote 的存储/查询/删除.

下面摘取一部分代码进行分析

// Cosmos-SDK/x/gov/keeper.gotype Keeper struct {// The reference to the CoinKeeper to modify balancesck 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的keeper结构体

  • BankKeeper 这是bank模块的keeper引用, 包含它可以调用bank模块的方法.例如在提案的Deposit阶段时处理验证人提交的押金
  • sdk.StoreKey 这是App层KVStore数据库的一个存储Key, 模块内通过它来操作存储的Value
  • *codec.Codec 这是一个codec的指针,提供Amino编解码接口
// 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).MaxDepositPeriodproposal.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 Proposalkeeper.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))
}

上面是有关Proposal的新建/查询/删除接口,还有Deposit和Vote相关的方法不详细展示。

3.2 msg and handler

msg 是模块内支持的通过交易发起的操做
在上面的治理逻辑中,需要定义的Msg包括 (SubmitProposal/Deposit)

  • SubmitProposal : 提交提案
  • Deposit : 为提案提供押金
  • Vote : 为提案投票
// Cosmos-SDK/x/gov/msg.go
// MsgDeposit
type MsgDeposit struct {ProposalID uint64         `json:"proposal_id"` // ID of the proposalDepositor  sdk.AccAddress `json:"depositor"`   // Address of the depositorAmount     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 中仅仅是定义消息类型和实现消息的一些接口,当模块收到对应的消息时,处理的函数实现在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 主要是将模块内自定义的类型注册到Amino,这样它们才会支持编解码。

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

Querier 中定义的是模块提供给外部的可供查询的接口,
在上面的逻辑中,querier包括

  • 查询当前存在的所有提案
  • 查询提案的押金数
  • 查询提案的投票数
  • 等等

baseapp中实现的ABCI接口 Query 是用来执行查询的,不同的查询请求是根据queryRouter 路由到对应的模块的,路由的原理和交易路由一样。

// 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 queriescase "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

上面的querier和handler已经完成了治理模块内请求和事务的处理流程,那么client模块就是给用户提供发起请求和事务的接口。

cli 目录是提供给App命令行操作的接口:

// 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 uintproposalID, 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 uintproposalID, 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 coinsamount, 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)},}
}

rest 目录下是提供的REST接口

注册所有提供的请求路径和对应的执行函数

// 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 DepositReqif !rest.ReadRESTReq(w, r, cdc, &req) {return}req.BaseReq = req.BaseReq.Sanitize()if !req.BaseReq.ValidateBasic(w) {return}// create the messagemsg := 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 导出该模块所有的client操作接口, 最终绑定到主程序的子命令中。

// Cosmos-SDK/x/gov/client/module_client.go
type ModuleClient struct {storeKey stringcdc      *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 subcommandgovQueryCmd := &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. 总结

本文分析了治理模块的代码之后,整理了Cosmos模块开发框架,对于想要编写Cosmos App 及插件的同学有较大帮助。

Cosmos系列-2. Cosmos SDK相关推荐

  1. android app初始化sdk,Android SDK使用系列教程——2.SDK初始化和常用类介绍

    本帖最后由 碎羽 于 2015-6-18 11:36 编辑 上次讲到SDK的下载和导入,这次来讲讲SDK的初始化和常用类的介绍. 一.初始化SDK 初始化SDK,首先要获得对应设备的AppID.App ...

  2. 知微传感Dkam系列3D相机SDK例程篇:点云滤波

    3D相机点云滤波 写在前面 本人从事机器视觉细分的3D相机行业.编写此系列文章主要目的有: 1.便利他人应用3D相机,本系列文章包含公司所出售相机的SDK的使用例程及详细注释: 2.促进行业发展及交流 ...

  3. 知微传感Dkam系列3D相机SDK例程篇:配置相机曝光

    配置3D相机曝光 写在前面 本人从事机器视觉细分的3D相机行业.编写此系列文章主要目的有: 1.便利他人应用3D相机,本系列文章包含公司所出售相机的SDK的使用例程及详细注释: 2.促进行业发展及交流 ...

  4. 知微传感Dkam系列3D相机SDK例程篇:获取内外参

    获取3D相机内外参 写在前面 本人从事机器视觉细分的3D相机行业.编写此系列文章主要目的有: 1.便利他人应用3D相机,本系列文章包含公司所出售相机的SDK的使用例程及详细注释: 2.促进行业发展及交 ...

  5. 【Xamarin 挖墙脚系列:Xamarin SDK开源了................】

                                                                  在前不久举行的 Build 2016 开发者大会上,微软宣布它收购的 Xam ...

  6. 【转】Azure Messaging-ServiceBus Messaging消息队列技术系列2-编程SDK入门

    各位,上一篇基本概念和架构中,我们介绍了Window Azure ServiceBus的消息队列技术的概览.接下来,我们进入编程模式和详细功能介绍模式,一点一点把ServiceBus技术研究出来. 本 ...

  7. 【HISI系列】之SDK编码器开发

    序  本文介绍hisi35xx系列,例如3559AV100编码器开发的大体流程框架. hisi开发经验:1.由于hisi每一款芯片对各编码协议(如H264/H265/JPEG/MOTION-JPEG) ...

  8. 某系列视频采集卡相关SDK的二次开发基础

    1.首先安装驱动: 2.不要下载/卸载自带软件(与SDK冲突): 3.安装CODECS: 4.寻找自己的采集卡型号配套SDK: (TC-UB530)请使用UB530.PRODUCTS 也可以使用D:\ ...

  9. 【先楫HPM6750系列】HPM SDK开发环境搭建和Hello World

    上篇帖子中,我们介绍了如何下载HPM6750开发所需的资料,包括开发板资料和SEGGER Embedded Studio安装包,以及如何申请Embedded Studio激活码.本篇将会介绍如何安装S ...

最新文章

  1. 青源 LIVE 第 20 期 | 复旦大学吴祖煊:高效视频内容识别
  2. Django的model模型
  3. 转: centos7 安装 juypter notebook 教程
  4. AntV中的饼状图重复渲染问题解决
  5. 使用机器学习预测天气_如何使用机器学习根据文章标题预测喜欢和分享
  6. 工作152:阅读之后台管理登录样式
  7. 多IDC GSLB的部署
  8. 使用arttemplate js模板引擎,直接用模板渲染,减少字符串拼接。
  9. 绘图神器 —— Graphviz(一)
  10. html参数转义字符,Html转义字符 获得请求参数
  11. 2020腾讯校园实习生招聘面经(Offer):系统技术运维岗和后台开发岗
  12. zheng win 环境搭建
  13. 怎么彻底卸载2345软件、怎么屏蔽2345弹窗
  14. the-craft-of-selfteaching之《自学是门手艺》开源项目书的学习笔记一篇
  15. app store无法下载、安装软件,一直在转圈的一个解决方法
  16. 吴乙己的数仓指南_2维度建模核心思想
  17. Linux模块(2) - 创建设备节点
  18. c语言编程 人造卫星的高度,C语言实验教学教案2008
  19. 代码详解:手把手教你建立自己的视频分类模型
  20. 搜狐狐友营销的十二个办法

热门文章

  1. 数字化医院建设的四大要领
  2. 数字孪生智慧医院:构建三维医疗管控系统
  3. 电脑软件:推荐七款实用的效率神器
  4. c#中正则表达式屏蔽一些特殊字符
  5. AST实战|免安装一键还原ob混淆详细使用教程
  6. 一个女孩主动加你微信,不说话也不打招呼是因为啥呢?
  7. DTOJ#5238. 庆生会
  8. es5与es6中如何处理不确定参数?以及es6中rest parameter的强大之处
  9. 第八周项目五 定期存款利息计算器
  10. 利用LightGBM实现天气变化的时间序列预测