Cosmos系列-2. Cosmos SDK
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简单的交易处理流程:
- 将从Tendermint共识引擎(DeliverTx)收到的交易进行解码
- 从交易中提取messages 并做基本的检查
- 将每个message路由到各自的模块进行处理
- 提交状态变化
// 处理交易的流程
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相关推荐
- android app初始化sdk,Android SDK使用系列教程——2.SDK初始化和常用类介绍
本帖最后由 碎羽 于 2015-6-18 11:36 编辑 上次讲到SDK的下载和导入,这次来讲讲SDK的初始化和常用类的介绍. 一.初始化SDK 初始化SDK,首先要获得对应设备的AppID.App ...
- 知微传感Dkam系列3D相机SDK例程篇:点云滤波
3D相机点云滤波 写在前面 本人从事机器视觉细分的3D相机行业.编写此系列文章主要目的有: 1.便利他人应用3D相机,本系列文章包含公司所出售相机的SDK的使用例程及详细注释: 2.促进行业发展及交流 ...
- 知微传感Dkam系列3D相机SDK例程篇:配置相机曝光
配置3D相机曝光 写在前面 本人从事机器视觉细分的3D相机行业.编写此系列文章主要目的有: 1.便利他人应用3D相机,本系列文章包含公司所出售相机的SDK的使用例程及详细注释: 2.促进行业发展及交流 ...
- 知微传感Dkam系列3D相机SDK例程篇:获取内外参
获取3D相机内外参 写在前面 本人从事机器视觉细分的3D相机行业.编写此系列文章主要目的有: 1.便利他人应用3D相机,本系列文章包含公司所出售相机的SDK的使用例程及详细注释: 2.促进行业发展及交 ...
- 【Xamarin 挖墙脚系列:Xamarin SDK开源了................】
在前不久举行的 Build 2016 开发者大会上,微软宣布它收购的 Xam ...
- 【转】Azure Messaging-ServiceBus Messaging消息队列技术系列2-编程SDK入门
各位,上一篇基本概念和架构中,我们介绍了Window Azure ServiceBus的消息队列技术的概览.接下来,我们进入编程模式和详细功能介绍模式,一点一点把ServiceBus技术研究出来. 本 ...
- 【HISI系列】之SDK编码器开发
序 本文介绍hisi35xx系列,例如3559AV100编码器开发的大体流程框架. hisi开发经验:1.由于hisi每一款芯片对各编码协议(如H264/H265/JPEG/MOTION-JPEG) ...
- 某系列视频采集卡相关SDK的二次开发基础
1.首先安装驱动: 2.不要下载/卸载自带软件(与SDK冲突): 3.安装CODECS: 4.寻找自己的采集卡型号配套SDK: (TC-UB530)请使用UB530.PRODUCTS 也可以使用D:\ ...
- 【先楫HPM6750系列】HPM SDK开发环境搭建和Hello World
上篇帖子中,我们介绍了如何下载HPM6750开发所需的资料,包括开发板资料和SEGGER Embedded Studio安装包,以及如何申请Embedded Studio激活码.本篇将会介绍如何安装S ...
最新文章
- 青源 LIVE 第 20 期 | 复旦大学吴祖煊:高效视频内容识别
- Django的model模型
- 转: centos7 安装 juypter notebook 教程
- AntV中的饼状图重复渲染问题解决
- 使用机器学习预测天气_如何使用机器学习根据文章标题预测喜欢和分享
- 工作152:阅读之后台管理登录样式
- 多IDC GSLB的部署
- 使用arttemplate js模板引擎,直接用模板渲染,减少字符串拼接。
- 绘图神器 —— Graphviz(一)
- html参数转义字符,Html转义字符 获得请求参数
- 2020腾讯校园实习生招聘面经(Offer):系统技术运维岗和后台开发岗
- zheng win 环境搭建
- 怎么彻底卸载2345软件、怎么屏蔽2345弹窗
- the-craft-of-selfteaching之《自学是门手艺》开源项目书的学习笔记一篇
- app store无法下载、安装软件,一直在转圈的一个解决方法
- 吴乙己的数仓指南_2维度建模核心思想
- Linux模块(2) - 创建设备节点
- c语言编程 人造卫星的高度,C语言实验教学教案2008
- 代码详解:手把手教你建立自己的视频分类模型
- 搜狐狐友营销的十二个办法