上次讲到 Tendermint 将网络层和共识层设计好并封装起来,提供给区块链开发者使用,在这种情况下开发一个链时只需要考虑应用层。 不过应用层中有很多功能还是可以通用的,比如账户管理/Token转账等功能。Cosmos把很多通用功能分解出来,以模块化的形式加以实现,例如:账户管理,社区治理,Staking等等,这就组成了一套新的区块链开发框架Cosmos-SDK。
开发者使用Cosmos-SDK进行应用开发时,只需要实现自身业务中特殊的功能,其他功能可以直接调用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
baseApp 是Cosmos-SDK 的ABCI接口应用的基础实现,它自带router
来路由交易到各个对应的模块,通过模块丰富App的功能。
回顾一下Tendermint提供的ABCI接口:
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
}
下面看一下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
...
}
baseApp 在封装了ABCI接口的基础上,预留了几个处理函数由App实现并在适当的时候被调用。
下面是一个基于CosmosSDK开发的App简单的交易处理流程:
// 处理交易的流程
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)
}
...
}
...
}
下面是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: 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]
}
Cosmos SDK 提供了一个Multistore 持久化存储。它允许开发者创建任意个KVStores, 用来分别不同模块的state。这些KVStores只能存储[]byte
类型的值,因此所有自定义的结构都要在存储之前使用Amino进行编码。
Amino 是Cosmos团队基于Google的Protobuffer开发的新的编码格式.
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
Cosmos 有一个内建的治理系统,该系统允许抵押通证的持有人参与提案投票。系统现在支持3种提案类型:
任何质押通证的持有人都能够提交一个提案。为了让一个提案获准公开投票,提议人必须要缴纳一定量的押金 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的函数
Cosmos治理系统中的数据结构
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
)
治理系统中的一些参数
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.
}
Keeper用来处理模块与存储的交互,包含模块大部分核心功能。
在上面的逻辑中,keeper需要处理 Proposal/Deposit/Vote 的存储/查询/删除.
下面摘取一部分代码进行分析
// 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的keeper结构体
// 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))
}
上面是有关Proposal的新建/查询/删除接口,还有Deposit和Vote相关的方法不详细展示。
msg 是模块内支持的通过交易发起的操做
在上面的治理逻辑中,需要定义的Msg包括 (SubmitProposal/Deposit)
// 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 中仅仅是定义消息类型和实现消息的一些接口,当模块收到对应的消息时,处理的函数实现在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,
}
}
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)
}
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 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(),
}
}
...
}
上面的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 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)
},
}
}
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 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 导出该模块所有的client操作接口, 最终绑定到主程序的子命令中。
// 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
}
本文分析了治理模块的代码之后,整理了Cosmos模块开发框架,对于想要编写Cosmos App 及插件的同学有较大帮助。