前言:
找了一个旧版本的cosmos-sdk
教程,目标是构建一个域名解析应用. 类似于Namecoin,ENS,Handshake这些模仿传统的DNS系统(map[domain]zonefile
)的应用。用户可以购买未被使用的域名,或是出售/交易这些域名。
参考网址如下:
完整项目源码: https://github.com/jerrychan807/cosmos-nameservice
开发环境配置:
使用旧版的go:
一开始使用1.18版本的go,在后面会报该错误panic: crypto/hmac: hash generation function does not produce unique values when using ebdcli keys to add account ,所以选择降级到1.15.15
版本的go,就不会出现该错误了.
1 2 3 4 5 6 7 8 9 10 11 wget https://golang.google.cn/dl/go1.15.15.linux-amd64.tar.gz tar -zxvf go1.15.15.linux-amd64.tar.gz -C /usr/ vim /etc/profile export GOROOT=/usr/goexport PATH=$GOROOT /bin:$PATH export GOPATH=/opt/go15export PATH=$(go env GOPATH)/bin:$PATH
通过修改环境变量的顺序,使得先读取到1.15.15
版本的go
1 2 [root@localhost opt] /opt/go15/bin:/usr/go/bin:/usr/go/bin:/root/.cargo/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/usr/local/go/bin:/opt/go/BIN:/opt/go/bin/:/opt/go/bin:/root/bin:/usr/go/bin:/opt/go15/bin:/usr/go/bin:/opt/go15/bin
1.18版本go的环境变量,记录一下,方便后面恢复:
1 2 3 4 5 6 export GOROOT=/usr/local/goexport PATH=$PATH :$GOROOT /binexport GOPATH=/opt/goexport PATH=$PATH :$GOPATH /BINexport PATH=$PATH :/opt/go/bin/export PATH=$PATH :$(go env GOPATH)/bin
源码构建starport:
starport v0.13.1官方安装readme
下载源码:
1 2 3 4 5 git clone -b v0.13.1 https://github.com/ignite/cli.git cd climake
报错,提示缺少packr2
命令
1 2 /bin/sh: packr2: command not found make: *** [build] Error 127
下载packr:
1 2 3 4 5 6 wget https://github.com/gobuffalo/packr/releases/download/v2.8.1/packr_2.8.1_linux_amd64.tar.gz tar -xf packr_2.8.1_linux_amd64.tar.gz mv packr2 /usr/bin/packr2
再次编译:
1 2 3 4 5 6 7 8 9 10 11 make [root@localhost build] /blockchain/cli/build [root@localhost build] starport version v0.13.1 linux/amd64 -build date : 2022-09-05T03:20:04 git object hash : b4e95f455cb9dfb35fcd9981786eb935cf9711c5 ln -s /blockchain/cli/build/starport /usr/bin/starport
程序目标:
你正在构建的应用程序的目标是让用户购买域名并为其设置解析的值,给定域名的所有者将是当前最高出价者。
区块链应用程序只是一个具有确定性的复制状态机
.
状态机是一个计算机科学概念,其中一台机器可以有多个状态,但一次只能有一个状态。
它遵循状态转换过程(或一组定义的过程),这是状态从旧状态或初始状态 ( S)
更改为新状态 ( S')
的唯一方式。
作为开发人员,你只需定义状态机(即状态,启动状态和触发状态转变的消息)
,Tendermint
将为你处理通过网络进行复制。
Tendermint
是一个与应用程序无关的引擎,负责处理区块链的网络层
和共识层
。实际上,这意味着Tendermint
负责传播和排序交易字节。Tendermint Core
依赖于拜占庭容错(BFT)算法来达成交易顺序的共识
Cosmos SDK
旨在帮助你构建状态机,构建应用层。
SDK是一个模块化框架
,意味着应用程序
是通过将一组可互操作的模块
集成在一起构建而成的。每个模块都包含自己的消息/交易处理器
,而SDK负责将每条消息路由
到其对应模块。
以下是nameservice应用程序所需的模块:
auth
: 此模块定义了账户和手续费,并为你应用程序的其余部分提供了访问这些功能的权限。
bank
: 此模块使得应用程序能够创建和管理token及余额。
nameservice
: 需要我们自己写的nameservice应用的核心逻辑。
现在,看一下应用程序的两个主要部分:state(状态)
和 message(消息)
类型。
State:
state
反映了特定时刻你的应用程序。它告诉了每个帐户拥有多少token,每个域名的所有者和价格,以及每个域名的解析值。
token 和帐户的 state 由auth
和bank
模块定义,这意味着你现在不必关心它。你需要做的是定义与你的nameservice
模块特定相关部分state。
在 SDK 中,所有内容都存储在一个名为multistore
的存储中。可以在此multistore
中创建任意数量的键值对存储(在Cosmos SDK中称作KVStore
)。
在本应用中,我们将使用一个 store 记录 name
与 whois
信息,name 的 value
、owner
和 price
将存储在一个结构中。
Message:
message
包含在 transaction
中。它们负责触发state的转变
。
每个模块定义了一个message
列表及如何去处理它们。
下面这些 message
是你需要为你的 nameservice
应用去实现的:
交易的处理流程:
当一条交易
(包含在区块中)到达一个Tendermint
节点时,它将通过 ABCI
传递给应用程序并被解码以得到 message
。
然后将message
路由至对应的模块module
,并根据定义在Handler
中的逻辑来进行处理。
如果 state
需要更新,Handler
会调用Keeper
来执行更新。
开始编写你的程序:
1 2 starport app github.com/jerrychan807/cosmos-nameservice --sdk-version="launchpad"
1 2 3 4 5 6 7 8 9 starport app github.com/jerrychan807/cosmos-nameservice --sdk-version="launchpad" ⭐️ Successfully created a Cosmos app 'cosmos-nameservice' . 👉 Get started with the following commands: % cd cosmos-nameservice % starport serve NOTE: add --verbose flag for verbose (detailed) output.
其中app.go
,这个文件是确定性状态机
的核心。
在app.go
中,你定义了应用程序在接收交易时执行的操作 。
但首先,它要能够以正确的顺序接收交易。这是 Tendermint共识引擎
的职责。
Tendermint
通过名为 ABCI
的接口将交易从网络传递给应用程序。
幸运的是,你不必实现ABCI接口。Cosmos SDK
以baseapp
的形式提供了区块链应用的雏形,我们只要基于baseapp
定制化进行开发即可。
baseapp做了以下几点:
解码从 Tendermint共识引擎
接收到的交易
。
从交易中提取 messages
并做基本的合理性校验。
将这些 message 路由
到合适的模块使其被正确处理。
如果 ABCI 消息是DeliverTx(CheckTx)
的话就Commit
。
帮助设置BeginBlock
和EndBlock
,这两种消息让你能定义在每个区块开始和结束时执行的逻辑。
帮助初始化你的 state
。
帮助设置 queries
。
baseapp
不能够识别用户自定义模块的路由和应用程序中自定义的用户接口,我们需要将我们的cosmosnameservicetypes
类型将嵌入到baseapp
中。
需要修改 app.go
为了完成应用程序,你需要引入一些模块.继续开始构建你的域名服务模块.稍后会回到app.go
.
类型Types:
我们要做的第一件事是定义一个结构,包含域名所有元数据。
1 2 starport type whois value price
1 2 3 [root@localhost cosmos-nameservice] 🎉 Created a type `whois`.
每个域名有以下相关的数据:
Value
: 域名解析出为的值
Creator
: 该域名当前所有者的地址
Price
: 你需要为购买域名支付的费用
1 2 3 4 5 6 type Whois struct { Creator sdk.AccAddress `json:"creator" yaml:"creator"` ID string `json:"id" yaml:"id"` Value string `json:"value" yaml:"value"` Price sdk.Coins `json:"price"` }
需要修改x/cosmosnameservice/types/TypeWhois.go
Keeper:
Cosmos SDK
模块的主要核心是名为Keeper
的部分。
它处理存储的交互
,引用其他的keeper进行跨模块的交互
,并包含模块的大部分核心功能。
Keeper结构:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 package keeperimport ( "fmt" "github.com/tendermint/tendermint/libs/log" "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/bank" "github.com/jerrychan807/cosmos-nameservice/x/cosmosnameservice/types" ) type Keeper struct { CoinKeeper bank.Keeper storeKey sdk.StoreKey cdc *codec.Codec }
需要修改x/cosmosnameservice/keeper/keeper.go
关于上述代码的几点说明:
3个不同的cosmos-sdk
包被引入:
Keeper结构体。在 keeper 中有几个关键部分:
bank.Keeper
: 这是bank模块的Keeper引用。包括它来允许该模块中的代码调用bank模块的函数。
SDK使用对象能力
来访问应用程序状态的各个部分。这是为了允许开发人员采用小权限准入原则,限制错误或恶意模块的去影响其不需要访问的状态的能力。
*codec.Codec
: 这是被Amino
用于编码及解码的指针。
sdk.StoreKey
: 通过它来访问一个持久化保存你的应用程序状态的sdk.KVStore
。
模块有1个StoreKey:
Getter 和 Setter:
现在要添加通过Keeper
来与存储交互的方法了。
首先,添加一个函数来为指定域名设置解析字符串值:
1 2 3 4 5 6 7 8 9 10 11 12 func (k Keeper) SetWhois(ctx sdk.Context, name string , whois types.Whois) { store := ctx.KVStore(k.storeKey) bz := k.cdc.MustMarshalBinaryLengthPrefixed(whois) key := []byte (types.WhoisPrefix + name) store.Set(key, bz) }
查找域名对应的解析值函数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 func (k Keeper) GetWhois(ctx sdk.Context, key string ) (types.Whois, error ) { store := ctx.KVStore(k.storeKey) var whois types.Whois byteKey := []byte (types.WhoisPrefix + key) err := k.cdc.UnmarshalBinaryLengthPrefixed(store.Get(byteKey), &whois) if err != nil { return whois, err } return whois, nil }
需要修改x/cosmosnameservice/keeper/whois.go
其他函数:
继续添加其他函数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 func (k Keeper) GetWhoisCount()func (k Keeper) SetWhoisCount()func (k Keeper) DeleteWhois()func (k Keeper) GetCreator()func (k Keeper) Exists()func (k Keeper) ResolveName()func (k Keeper) SetName()func (k Keeper) HasCreator()func (k Keeper) SetCreator()func (k Keeper) GetPrice()func (k Keeper) SetPrice()func (k Keeper) IsNamePresent()func (k Keeper) GetNamesIterator()func (k Keeper) GetWhoisOwner()func (k Keeper) WhoisExists()
接下来,该描述如何让用户通过Msgs
和Handlers
与刚刚建立的store
交互。
Msg 和 Handler:
现在你已经设置了Keeper
,是时候构建允许用户购买域名和设置解析值的Msg
和Handler
了。
Msg:
Msg
触发状态转变。Msgs
被包裹在客户端提交至网络的Txs
中。
Cosmos SDK从Txs
中打包和解包Msgs
,这就意味着,作为一个应用开发者,你只需要去定义Msgs
。
Msg必须要满足以下接口:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 package typesimport ( sdk "github.com/cosmos/cosmos-sdk/types" ) type Msg interface { Type() string Route() string ValidateBasic() error GetSignBytes() []byte GetSigners() []sdk.AccAddress }
需要修改./x/cosmosnameservice/types/msg.go
Handler:
Handler
定义了在接收到一个特定Msg
时,需要采取的操作(哪些存储需要更新,怎样更新及要满足什么条件),可看作为controller
。
在此模块中,你有两种类型的Msg
,用户可以发送这些Msg
来和应用程序状态进行交互:SetName
和BuyName
。它们各自同其Handler
关联。
现在你已经更好地理解了 Msgs
和Handler
,可以开始构建你的第一条消息:SetName
。
MsgSetName:
SDK中Msg的命令约束是 Msg{.Action}
。要实现的第一个操作是SetName
,因此命名为MsgSetName
。
首先实现SetName
, 这个消息Msg允许域名的所有者在解析器中设置该域名的返回值。
需要修改./x/cosmosnameservice/types/MsgSetName.go
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 package typesimport ( sdk "github.com/cosmos/cosmos-sdk/types" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" ) type MsgSetName struct { Name string `json:"name"` Value string `json:"value"` Owner sdk.AccAddress `json:"owner"` } func NewMsgSetName (name string , value string , owner sdk.AccAddress) MsgSetName { return MsgSetName{ Name: name, Value: value, Owner: owner, } }
MsgSetName具有设置域名解析值所需的三个属性:
name
: 所要设置的域名
value
: 要设置的域名解析值
owner
: 域名的所有者
接下来,实现Msg
接口:
Route和Type接口:
1 2 3 4 5 6 7 func (msg MsgSetName) Route() string { return RouterKey }func (msg MsgSetName) Type() string { return "set_name" }
SDK使用上述函数将Msg路由至合适的模块进行处理。它们还为用于索引的数据库标签添加了可读性的名称。
ValidateBasic接口:
1 2 3 4 5 6 7 8 9 10 11 func (msg MsgSetName) ValidateBasic() error { if msg.Owner.Empty() { return sdkerrors.Wrap(sdkerrors.ErrInvalidAddress, msg.Owner.String()) } if len (msg.Name) == 0 || len (msg.Value) == 0 { return sdkerrors.Wrap(sdkerrors.ErrUnknownRequest, "Name and/or Value cannot be empty" ) } return nil }
ValidateBasic
用于对Msg
的有效性进行一些基本的无状态检查 。
在此情形下,请检查没有属性为空。
请注意这里使用sdk.Error
类型,SDK提供了一组应用开发人员经常遇到的错误类型。
GetSignBytes接口:
1 2 3 4 5 6 7 func (msg MsgSetName) GetSignBytes() []byte { return sdk.MustSortJSON(ModuleCdc.MustMarshalJSON(msg)) }
GetSignBytes
定义了如何编码Msg以进行签名。
在大多数情形下,要编码成排好序的JSON。不应修改输出。
GetSigners接口:
1 2 3 4 5 func (msg MsgSetName) GetSigners() []sdk.AccAddress { return []sdk.AccAddress{msg.Owner} }
GetSigners
定义一个Tx
上需要哪些人的签名才能使其有效。
在这种情形下,MsgSetName要求域名所有者在尝试重置域名解析值时要对该交易签名。
handlerMsgSetName:
目前MsgSetName已经规定好了, 当接收到了消息后Handler负责定义接下来的行动.
NewHandler本质
上是一个子路由器 ,它将进入该模块的消息分配给适当的处理程序。
目前,只有一个Msg/Handler
.
注意:SDK中handler的命名规范是handlerMsg{.Action}
需要修改x/cosmosnameservice/handlerMsgSetName.go
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 package cosmosnameserviceimport ( sdk "github.com/cosmos/cosmos-sdk/types" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" "github.com/jerrychan807/cosmos-nameservice/x/cosmosnameservice/keeper" "github.com/jerrychan807/cosmos-nameservice/x/cosmosnameservice/types" ) func handleMsgSetName (ctx sdk.Context, keeper keeper.Keeper, msg types.MsgSetName) (*sdk.Result, error ) { if !msg.Owner.Equals(keeper.GetWhoisOwner(ctx, msg.Name)) { return nil , sdkerrors.Wrap(sdkerrors.ErrUnauthorized, "Incorrect Owner" ) } keeper.SetName(ctx, msg.Name, msg.Value) return &sdk.Result{}, nil }
handler.go
是各个handlerxxxx.go
的总路由 , 所以每一个对应的msg
都需要在这里注册, 下面是修改完整的handler.go
:
需要修改x/cosmosnameservice/handler.go
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 package cosmosnameserviceimport ( "fmt" sdk "github.com/cosmos/cosmos-sdk/types" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" "github.com/jerrychan807/cosmos-nameservice/x/cosmosnameservice/keeper" "github.com/jerrychan807/cosmos-nameservice/x/cosmosnameservice/types" ) func NewHandler (keeper keeper.Keeper) sdk.Handler { return func (ctx sdk.Context, msg sdk.Msg) (*sdk.Result, error ) { switch msg := msg.(type ) { case types.MsgSetName: return handleMsgSetName(ctx, keeper, msg) case types.MsgBuyName: return handleMsgBuyName(ctx, keeper, msg) case types.MsgDeleteName: return handleMsgDeleteName(ctx, keeper, msg) default : return nil , sdkerrors.Wrap(sdkerrors.ErrUnknownRequest, fmt.Sprintf("Unrecognized nameservice Msg type: %v" , msg.Type())) } } }
现在, 拥有者可以设置域名的解析了, 但是如果是一个域名不存在拥有者的情况呢?
现在模块需要提供一个途径让用户去购买域名, 下面就定义BuyName
的Msg
MsgBuyName:
需要修改./x/cosmosnameservice/types/MsgBuyName.go
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 package typesimport ( sdk "github.com/cosmos/cosmos-sdk/types" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" ) type MsgBuyName struct { Name string `json:"name"` Bid sdk.Coins `json:"bid"` Buyer sdk.AccAddress `json:"buyer"` } func NewMsgBuyName (name string , bid sdk.Coins, buyer sdk.AccAddress) MsgBuyName { return MsgBuyName{ Name: name, Bid: bid, Buyer: buyer, } } func (msg MsgBuyName) Route() string { return RouterKey }func (msg MsgBuyName) Type() string { return "buy_name" }func (msg MsgBuyName) ValidateBasic() error { if msg.Buyer.Empty() { return sdkerrors.Wrap(sdkerrors.ErrInvalidAddress, msg.Buyer.String()) } if len (msg.Name) == 0 { return sdkerrors.Wrap(sdkerrors.ErrUnknownRequest, "Name cannot be empty" ) } if !msg.Bid.IsAllPositive() { return sdkerrors.ErrInsufficientFunds } return nil } func (msg MsgBuyName) GetSignBytes() []byte { return sdk.MustSortJSON(ModuleCdc.MustMarshalJSON(msg)) } func (msg MsgBuyName) GetSigners() []sdk.AccAddress { return []sdk.AccAddress{msg.Buyer} }
handlerMsgBuyName:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 package cosmosnameserviceimport ( sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/jerrychan807/cosmos-nameservice/x/cosmosnameservice/keeper" "github.com/jerrychan807/cosmos-nameservice/x/cosmosnameservice/types" ) func handleMsgBuyName (ctx sdk.Context, k keeper.Keeper, msg types.MsgBuyName) (*sdk.Result, error ) { if k.GetPrice(ctx, msg.Name).IsAllGT(msg.Bid) { return nil , sdkerrors.Wrap(sdkerrors.ErrInsufficientFunds, "Bid not high enough" ) } if k.HasCreator(ctx, msg.Name) { err := k.CoinKeeper.SendCoins(ctx, msg.Buyer, k.GetCreator(ctx, msg.Name), msg.Bid) if err != nil { return nil , err } } else { _, err := k.CoinKeeper.SubtractCoins(ctx, msg.Buyer, msg.Bid) if err != nil { return nil , err } } k.SetCreator(ctx, msg.Name, msg.Buyer) k.SetPrice(ctx, msg.Name, msg.Bid) return &sdk.Result{}, nil }
同上,继续编写类似的MsgDeleteName
和handlerMsgDeleteName
。
现在已经有了Msgs
和Handlers
定义,是时候学习如何使交易中的数据能被查询到!
Querier:
首先创建./x/cosmosnameservice/keeper/querier.go
文件。在这里定义应用程序用户可以对那些状态进行查询。
你的nameservice
模块会暴露3个querier
:
resolve
: 解析值
getWhois
: 查询域名的所有相关信息
listWhois
: 查询所有的已存在域名
首先定义NewQuerier
函数,该函数充当查询此模块的子路由器(类似于NewHandler
函数)。
请注意,因为querier没有类似于Msg的接口,所以需要手动定义switch语句(它们无法从query.Route()
函数中删除):
需要修改x/cosmosnameservice/keeper/querier.go
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 package keeperimport ( "github.com/jerrychan807/cosmos-nameservice/x/cosmosnameservice/types" abci "github.com/tendermint/tendermint/abci/types" sdk "github.com/cosmos/cosmos-sdk/types" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" ) func NewQuerier (k Keeper) sdk.Querier { return func (ctx sdk.Context, path []string , req abci.RequestQuery) ([]byte , error ) { switch path[0 ] { case types.QueryResolveName: return resolveName(ctx, path[1 :], k) case types.QueryListWhois: return listWhois(ctx, k) case types.QueryGetWhois: return getWhois(ctx, path[1 :], k) default : return nil , sdkerrors.Wrap(sdkerrors.ErrUnknownRequest, "unknown nameservice query endpoint" ) } } }
现在已定义路由器,为每个查询定义输入和响应:
需要修改x/cosmosnameservice/types/whois.go
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 func listWhois (ctx sdk.Context, k Keeper) ([]byte , error ) { var whoisList []types.Whois store := ctx.KVStore(k.storeKey) iterator := sdk.KVStorePrefixIterator(store, []byte (types.WhoisPrefix)) for ; iterator.Valid(); iterator.Next() { var whois types.Whois k.cdc.MustUnmarshalBinaryLengthPrefixed(store.Get(iterator.Key()), &whois) whoisList = append (whoisList, whois) } res := codec.MustMarshalJSONIndent(k.cdc, whoisList) return res, nil } func resolveName (ctx sdk.Context, path []string , keeper Keeper) ([]byte , error ) { value := keeper.ResolveName(ctx, path[0 ]) if value == "" { return []byte {}, sdkerrors.Wrap(sdkerrors.ErrUnknownRequest, "could not resolve name" ) } res, err := codec.MarshalJSONIndent(keeper.cdc, types.QueryResResolve{Value: value}) if err != nil { return nil , sdkerrors.Wrap(sdkerrors.ErrJSONMarshal, err.Error()) } return res, nil } func getWhois (ctx sdk.Context, path []string , k Keeper) (res []byte , sdkError error ) { key := path[0 ] whois, err := k.GetWhois(ctx, key) if err != nil { return nil , err } res, err = codec.MarshalJSONIndent(k.cdc, whois) if err != nil { return nil , sdkerrors.Wrap(sdkerrors.ErrJSONMarshal, err.Error()) } return res, nil }
在这里,你的Keeper
的getter
和setter
被大量使用。
当构建任何其他使用此模块的应用程序时,您可能需要返回并定义更多的getter/setter
来访问您需要的状态片段。
按照约定,每个输出类型都应该是JSON marshallable
和stringable
(实现了Golang fmt接口)。返回的字节应该是输出结果的JSON编码。
因此,对于resolve
的输出类型,我们将解析字符串包装在一个名为QueryResResolve
的结构中,该结构既是JSON marshallable
的,又有.string()
方法。
对于Whois
的输出,正常的Whois结构已经是JSON marshallable
的,但是我们需要在其上添加一个.string()
方法。
对于names
查询的输出也是一样的,[]字符串已经是本机可编组的,但是我们想在其上添加一个. string()方法。
既然你有办法改变和查看模块状态,那么现在是时候完成它了! 接下来以Amino编码格式
注册类型!
Codec文件:
在Amino
中注册你的数据类型使得它们能够被编码/解码
。
你创建的任何接口和实现接口的任何结构都需要在RegisterCodec
函数中声明。
在此模块中,需要注册三个Msg的实现(SetName
, BuyName
and DeleteName
)。
需要修改x/cosmosnameservice/types/codec.go
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 package typesimport ( "github.com/cosmos/cosmos-sdk/codec" ) func RegisterCodec (cdc *codec.Codec) { cdc.RegisterConcrete(MsgBuyName{}, "cosmosnameservice/BuyName" , nil ) cdc.RegisterConcrete(MsgSetName{}, "cosmosnameservice/SetName" , nil ) cdc.RegisterConcrete(MsgDeleteName{}, "cosmosnameservice/DeleteName" , nil ) } var ModuleCdc *codec.Codecfunc init () { ModuleCdc = codec.New() RegisterCodec(ModuleCdc) codec.RegisterCrypto(ModuleCdc) ModuleCdc.Seal() }
Nameservice模块的CLI:
Cosmos SDK使用cobra
库进行CLI交互。该库使每个模块都可以轻松地显示自己的操作命令。
要开始定义用户与模块的CLI交互,请创建以下文件:
Queries:
实现查询的客户端命令逻辑:
需要修改x/cosmosnameservice/client/cli/queryWhois.go
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 package cliimport ( "fmt" "github.com/cosmos/cosmos-sdk/client/context" "github.com/cosmos/cosmos-sdk/codec" "github.com/jerrychan807/cosmos-nameservice/x/cosmosnameservice/types" "github.com/spf13/cobra" ) func GetCmdListWhois (queryRoute string , cdc *codec.Codec) *cobra.Command { return &cobra.Command{ Use: "list-whois" , Short: "list all whois" , RunE: func (cmd *cobra.Command, args []string ) error { cliCtx := context.NewCLIContext().WithCodec(cdc) res, _, err := cliCtx.QueryWithData(fmt.Sprintf("custom/%s/%s" , queryRoute, types.QueryListWhois), nil ) if err != nil { fmt.Printf("could not list Whois\n%s\n" , err.Error()) return nil } var out []types.Whois cdc.MustUnmarshalJSON(res, &out) return cliCtx.PrintOutput(out) }, } } func GetCmdGetWhois (queryRoute string , cdc *codec.Codec) *cobra.Command { return &cobra.Command{ Use: "get-whois [key]" , Short: "Query a whois by key" , Args: cobra.ExactArgs(1 ), RunE: func (cmd *cobra.Command, args []string ) error { cliCtx := context.NewCLIContext().WithCodec(cdc) key := args[0 ] res, _, err := cliCtx.QueryWithData(fmt.Sprintf("custom/%s/%s/%s" , queryRoute, types.QueryGetWhois, key), nil ) if err != nil { fmt.Printf("could not resolve whois %s \n%s\n" , key, err.Error()) return nil } var out types.Whois cdc.MustUnmarshalJSON(res, &out) return cliCtx.PrintOutput(out) }, } } func GetCmdResolveName (queryRoute string , cdc *codec.Codec) *cobra.Command { return &cobra.Command{ Use: "resolve [name]" , Short: "resolve name" , Args: cobra.ExactArgs(1 ), RunE: func (cmd *cobra.Command, args []string ) error { cliCtx := context.NewCLIContext().WithCodec(cdc) name := args[0 ] res, _, err := cliCtx.QueryWithData(fmt.Sprintf("custom/%s/%s/%s" , queryRoute, types.QueryResolveName, name), nil ) if err != nil { fmt.Printf("could not resolve name - %s \n" , name) return nil } var out types.QueryResResolve cdc.MustUnmarshalJSON(res, &out) return cliCtx.PrintOutput(out) }, } }
注意上述代码中:
继续在cli/query.go
中添加子命令:
需要修改x/cosmosnameservice/client/cli/query.go
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 package cliimport ( "fmt" "github.com/spf13/cobra" "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/client/flags" "github.com/cosmos/cosmos-sdk/codec" "github.com/jerrychan807/cosmos-nameservice/x/cosmosnameservice/types" ) func GetQueryCmd (queryRoute string , cdc *codec.Codec) *cobra.Command { nameserviceQueryCmd := &cobra.Command{ Use: types.ModuleName, Short: fmt.Sprintf("Querying commands for the %s module" , types.ModuleName), DisableFlagParsing: true , SuggestionsMinimumDistance: 2 , RunE: client.ValidateCmd, } nameserviceQueryCmd.AddCommand( flags.GetCommands( GetCmdListWhois(queryRoute, cdc), GetCmdGetWhois(queryRoute, cdc), GetCmdResolveName(queryRoute, cdc), )..., ) return nameserviceQueryCmd }
Transaction:
接下来实现交易命令的客户端。
需要修改x/cosmosnameservice/client/cli/txWhois.go
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 package cliimport ( "bufio" "github.com/spf13/cobra" "github.com/cosmos/cosmos-sdk/client/context" "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/auth" "github.com/cosmos/cosmos-sdk/x/auth/client/utils" "github.com/jerrychan807/cosmos-nameservice/x/cosmosnameservice/types" ) func GetCmdBuyName (cdc *codec.Codec) *cobra.Command { return &cobra.Command{ Use: "buy-name [name] [price]" , Short: "Buys a new name" , Args: cobra.ExactArgs(2 ), RunE: func (cmd *cobra.Command, args []string ) error { argsName := string (args[0 ]) cliCtx := context.NewCLIContext().WithCodec(cdc) inBuf := bufio.NewReader(cmd.InOrStdin()) txBldr := auth.NewTxBuilderFromCLI(inBuf).WithTxEncoder(utils.GetTxEncoder(cdc)) coins, err := sdk.ParseCoins(args[1 ]) if err != nil { return err } msg := types.NewMsgBuyName(argsName, coins, cliCtx.GetFromAddress()) err = msg.ValidateBasic() if err != nil { return err } return utils.GenerateOrBroadcastMsgs(cliCtx, txBldr, []sdk.Msg{msg}) }, } } func GetCmdSetWhois (cdc *codec.Codec) *cobra.Command { return &cobra.Command{ Use: "set-name [value] [name]" , Short: "Set a new name" , Args: cobra.ExactArgs(2 ), RunE: func (cmd *cobra.Command, args []string ) error { argsValue := args[0 ] argsName := args[1 ] cliCtx := context.NewCLIContext().WithCodec(cdc) inBuf := bufio.NewReader(cmd.InOrStdin()) txBldr := auth.NewTxBuilderFromCLI(inBuf).WithTxEncoder(utils.GetTxEncoder(cdc)) msg := types.NewMsgSetName(argsName, argsValue, cliCtx.GetFromAddress()) err := msg.ValidateBasic() if err != nil { return err } return utils.GenerateOrBroadcastMsgs(cliCtx, txBldr, []sdk.Msg{msg}) }, } } func GetCmdDeleteWhois (cdc *codec.Codec) *cobra.Command { return &cobra.Command{ Use: "delete-name [id]" , Short: "Delete a new name by ID" , Args: cobra.ExactArgs(1 ), RunE: func (cmd *cobra.Command, args []string ) error { cliCtx := context.NewCLIContext().WithCodec(cdc) inBuf := bufio.NewReader(cmd.InOrStdin()) txBldr := auth.NewTxBuilderFromCLI(inBuf).WithTxEncoder(utils.GetTxEncoder(cdc)) msg := types.NewMsgDeleteName(args[0 ], cliCtx.GetFromAddress()) err := msg.ValidateBasic() if err != nil { return err } return utils.GenerateOrBroadcastMsgs(cliCtx, txBldr, []sdk.Msg{msg}) }, } }
需要修改x/cosmosnameservice/client/cli/tx.go
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 package cliimport ( "fmt" "github.com/spf13/cobra" "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/client/flags" "github.com/cosmos/cosmos-sdk/codec" "github.com/jerrychan807/cosmos-nameservice/x/cosmosnameservice/types" ) func GetTxCmd (cdc *codec.Codec) *cobra.Command { nameserviceTxCmd := &cobra.Command{ Use: types.ModuleName, Short: fmt.Sprintf("%s transactions subcommands" , types.ModuleName), DisableFlagParsing: true , SuggestionsMinimumDistance: 2 , RunE: client.ValidateCmd, } nameserviceTxCmd.AddCommand(flags.PostCommands( GetCmdBuyName(cdc), GetCmdSetWhois(cdc), GetCmdDeleteWhois(cdc), )...) return nameserviceTxCmd }
注意在上述代码中:
NameService模块的REST接口:
你的模块还可以公开 REST 接口,提供程序访问模块的功能。。首先创建一个文件来保存HTTP的handler
:
RegisterRoutes:
首先在RegisterRoutes
函数中为模块定义REST客户端接口。路由都以模块名称开头,以防止命名空间与其他模块的路径冲突:
需要修改./x/cosmosnameservice/client/rest/rest.go
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 package restimport ( "github.com/gorilla/mux" "github.com/cosmos/cosmos-sdk/client/context" ) func RegisterRoutes (cliCtx context.CLIContext, r *mux.Router) { r.HandleFunc("/nameservice/whois" , buyNameHandler(cliCtx)).Methods("POST" ) r.HandleFunc("/nameservice/whois" , listWhoisHandler(cliCtx, "nameservice" )).Methods("GET" ) r.HandleFunc("/nameservice/whois/{key}" , getWhoisHandler(cliCtx, "nameservice" )).Methods("GET" ) r.HandleFunc("/nameservice/whois/{key}/resolve" , resolveNameHandler(cliCtx, "nameservice" )).Methods("GET" ) r.HandleFunc("/nameservice/whois" , setWhoisHandler(cliCtx)).Methods("PUT" ) r.HandleFunc("/nameservice/whois" , deleteWhoisHandler(cliCtx)).Methods("DELETE" ) }
Query Handlers:
接下来,是时候定义上面提到的处理程序了。这些与前面定义的CLI方法非常相似。
需要修改x/cosmosnameservice/client/rest/queryWhois.go
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 package restimport ( "fmt" "github.com/jerrychan807/cosmos-nameservice/x/cosmosnameservice/types" "net/http" "github.com/cosmos/cosmos-sdk/client/context" "github.com/cosmos/cosmos-sdk/types/rest" "github.com/gorilla/mux" ) func listWhoisHandler (cliCtx context.CLIContext, storeName string ) http.HandlerFunc { return func (w http.ResponseWriter, r *http.Request) { res, _, err := cliCtx.QueryWithData(fmt.Sprintf("custom/%s/%s" , storeName, types.QueryListWhois), nil ) if err != nil { rest.WriteErrorResponse(w, http.StatusNotFound, err.Error()) return } rest.PostProcessResponse(w, cliCtx, res) } } func getWhoisHandler (cliCtx context.CLIContext, storeName string ) http.HandlerFunc { return func (w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) key := vars["key" ] res, _, err := cliCtx.QueryWithData(fmt.Sprintf("custom/%s/%s/%s" , storeName, types.QueryGetWhois, key), nil ) if err != nil { rest.WriteErrorResponse(w, http.StatusNotFound, err.Error()) return } rest.PostProcessResponse(w, cliCtx, res) } } func resolveNameHandler (cliCtx context.CLIContext, storeName string ) http.HandlerFunc { return func (w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) paramType := vars["key" ] res, _, err := cliCtx.QueryWithData(fmt.Sprintf("custom/%s/%s/%s" , storeName, types.QueryResolveName, paramType), nil ) if err != nil { rest.WriteErrorResponse(w, http.StatusNotFound, err.Error()) return } rest.PostProcessResponse(w, cliCtx, res) } }
上述代码要注意:
Tx Handlers:
现在定义 buyName
和 setName
交易路由。
请注意,这些实际上并不能将交易发送进行买入和设置名称。 这需要跟交易请求一起发送发送密码,将是一个安全问题。
相反,这些端点构建并返回每个特定交易,然后可以以安全的方式对其进行签名,然后使用标准端点(如/txs)将其广播到网络。
需要修改x/cosmosnameservice/client/rest/txWhois.go
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 package restimport ( "fmt" "github.com/cosmos/cosmos-sdk/client/context" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/types/rest" "github.com/cosmos/cosmos-sdk/x/auth/client/utils" "github.com/jerrychan807/cosmos-nameservice/x/cosmosnameservice/types" "net/http" ) type buyNameRequest struct { BaseReq rest.BaseReq `json:"base_req"` Buyer string `json:"buyer"` Name string `json:"name"` Price string `json:"price"` } func buyNameHandler (cliCtx context.CLIContext) http.HandlerFunc { return func (w http.ResponseWriter, r *http.Request) { var req buyNameRequest if !rest.ReadRESTReq(w, r, cliCtx.Codec, &req) { rest.WriteErrorResponse(w, http.StatusBadRequest, "failed to parse request" ) return } baseReq := req.BaseReq.Sanitize() if !baseReq.ValidateBasic(w) { return } addr, err := sdk.AccAddressFromBech32(req.Buyer) fmt.Println(addr) if err != nil { rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) return } coins, err := sdk.ParseCoins(req.Price) if err != nil { rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) return } msg := types.NewMsgBuyName(req.Name, coins, addr) err = msg.ValidateBasic() if err != nil { rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) return } utils.WriteGenerateStdTxResponse(w, cliCtx, baseReq, []sdk.Msg{msg}) } } type setWhoisRequest struct { BaseReq rest.BaseReq `json:"base_req"` Name string `json:"name"` Value string `json:"value"` Creator string `json:"creator"` } func setWhoisHandler (cliCtx context.CLIContext) http.HandlerFunc { return func (w http.ResponseWriter, r *http.Request) { var req setWhoisRequest if !rest.ReadRESTReq(w, r, cliCtx.Codec, &req) { rest.WriteErrorResponse(w, http.StatusBadRequest, "failed to parse request" ) return } baseReq := req.BaseReq.Sanitize() if !baseReq.ValidateBasic(w) { return } addr, err := sdk.AccAddressFromBech32(req.Creator) if err != nil { rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) return } msg := types.NewMsgSetName(req.Name, req.Value, addr) err = msg.ValidateBasic() if err != nil { rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) return } utils.WriteGenerateStdTxResponse(w, cliCtx, baseReq, []sdk.Msg{msg}) } } type deleteWhoisRequest struct { BaseReq rest.BaseReq `json:"base_req"` Owner string `json:"owner"` Name string `json:"name"` } func deleteWhoisHandler (cliCtx context.CLIContext) http.HandlerFunc { return func (w http.ResponseWriter, r *http.Request) { var req deleteWhoisRequest if !rest.ReadRESTReq(w, r, cliCtx.Codec, &req) { rest.WriteErrorResponse(w, http.StatusBadRequest, "failed to parse request" ) return } baseReq := req.BaseReq.Sanitize() if !baseReq.ValidateBasic(w) { return } addr, err := sdk.AccAddressFromBech32(req.Owner) if err != nil { rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) return } msg := types.NewMsgDeleteName(req.Name, addr) err = msg.ValidateBasic() if err != nil { rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) return } utils.WriteGenerateStdTxResponse(w, cliCtx, baseReq, []sdk.Msg{msg}) } }
注意上述代码:
现在你的模块已经具备了全部功能,需要与 Cosmos SDK 应用合并。
AppModule Interface:
Cosmos SDK为模块提供了一个标准接口。这个AppModule
接口要求模块提供一组ModuleBasicsManager
使用的方法,以便将它们合并到你的应用程序中。
相关文件: x/cosmosnameservice/module.go
Genesis:
AppModule
接口包含了许多用于初始化和导出初始化状态的区块链函数。
当启动、停止或导出链时,ModuleBasicManager
在每个模块上调用这些函数。下面是一个非常基本的实现,您可以对其进行扩展。
在 x/cosmosnameservice/types/genesis.go
。我们会定义初始状态是什么,默认的初始状态以及验证它的方法这样我们就不会遇到任何错误当我们以预先存在的状态开始链的时候。
相关文件: x/cosmosnameservice/types/genesis.go
关于上述代码的一些注意事项:
ValidateGenesis()
: 验证提供的生成状态,以确保所期望的不变量保持不变
DefaultGenesisState()
: 主要用于测试。这提供了一个最小的起源状态。
在链启动时调用InitGenesis()
,该函数将生成状态导入到keeper中。
ExportGenesis()
在停止链后被调用,此函数将应用程序状态加载到GenesisState
结构中,以便稍后导出到genesis.json
以及来自其他模块的数据。
引入你的模块并完成程序:
现在你的模块已就绪,它可以和其它两个模块auth和bank被合并到./app.go文件中:
需要修改app/app.go
接下来,你需要在NewApp
结构体中添加存储的key
和Keepers
:
NewApp结构体:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 type NewApp struct { *bam.BaseApp cdc *codec.Codec invCheckPeriod uint keys map [string ]*sdk.KVStoreKey tKeys map [string ]*sdk.TransientStoreKey subspaces map [string ]params.Subspace accountKeeper auth.AccountKeeper bankKeeper bank.Keeper stakingKeeper staking.Keeper supplyKeeper supply.Keeper paramsKeeper params.Keeper nameserviceKeeper cosmosnameservicekeeper.Keeper mm *module.Manager sm *module.SimulationManager } var _ simapp.App = (*NewApp)(nil )
构造函数:
并更新构造函数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 func NewInitApp ( logger log.Logger, db dbm.DB, traceStore io.Writer, loadLatest bool , invCheckPeriod uint , baseAppOptions ...func (*bam.BaseApp) ,) *NewApp { cdc := MakeCodec() bApp := bam.NewBaseApp(appName, logger, db, auth.DefaultTxDecoder(cdc), baseAppOptions...) bApp.SetCommitMultiStoreTracer(traceStore) bApp.SetAppVersion(version.Version) keys := sdk.NewKVStoreKeys( bam.MainStoreKey, auth.StoreKey, staking.StoreKey, supply.StoreKey, params.StoreKey, cosmosnameservicetypes.StoreKey, ) tKeys := sdk.NewTransientStoreKeys(staking.TStoreKey, params.TStoreKey) var app = &NewApp{ BaseApp: bApp, cdc: cdc, invCheckPeriod: invCheckPeriod, keys: keys, tKeys: tKeys, subspaces: make (map [string ]params.Subspace), } app.paramsKeeper = params.NewKeeper(app.cdc, keys[params.StoreKey], tKeys[params.TStoreKey]) app.subspaces[auth.ModuleName] = app.paramsKeeper.Subspace(auth.DefaultParamspace) app.subspaces[bank.ModuleName] = app.paramsKeeper.Subspace(bank.DefaultParamspace) app.subspaces[staking.ModuleName] = app.paramsKeeper.Subspace(staking.DefaultParamspace) app.accountKeeper = auth.NewAccountKeeper( app.cdc, keys[auth.StoreKey], app.subspaces[auth.ModuleName], auth.ProtoBaseAccount, ) app.bankKeeper = bank.NewBaseKeeper( app.accountKeeper, app.subspaces[bank.ModuleName], app.ModuleAccountAddrs(), ) app.supplyKeeper = supply.NewKeeper( app.cdc, keys[supply.StoreKey], app.accountKeeper, app.bankKeeper, maccPerms, ) stakingKeeper := staking.NewKeeper( app.cdc, keys[staking.StoreKey], app.supplyKeeper, app.subspaces[staking.ModuleName], ) app.stakingKeeper = *stakingKeeper.SetHooks( staking.NewMultiStakingHooks(), ) app.nameserviceKeeper = cosmosnameservicekeeper.NewKeeper( app.bankKeeper, app.cdc, keys[cosmosnameservicetypes.StoreKey], ) app.mm = module.NewManager( genutil.NewAppModule(app.accountKeeper, app.stakingKeeper, app.BaseApp.DeliverTx), auth.NewAppModule(app.accountKeeper), bank.NewAppModule(app.bankKeeper, app.accountKeeper), supply.NewAppModule(app.supplyKeeper, app.accountKeeper), cosmosnameservice.NewAppModule(app.nameserviceKeeper, app.bankKeeper), staking.NewAppModule(app.stakingKeeper, app.accountKeeper, app.supplyKeeper), ) app.mm.SetOrderEndBlockers(staking.ModuleName) app.mm.SetOrderInitGenesis( staking.ModuleName, auth.ModuleName, bank.ModuleName, cosmosnameservicetypes.ModuleName, supply.ModuleName, genutil.ModuleName, ) app.mm.RegisterRoutes(app.Router(), app.QueryRouter()) app.SetInitChainer(app.InitChainer) app.SetBeginBlocker(app.BeginBlocker) app.SetEndBlocker(app.EndBlocker) app.SetAnteHandler( auth.NewAnteHandler( app.accountKeeper, app.supplyKeeper, auth.DefaultSigVerificationGasConsumer, ), ) app.MountKVStores(keys) app.MountTransientStores(tKeys) if loadLatest { err := app.LoadLatestVersion(app.keys[bam.MainStoreKey]) if err != nil { tmos.Exit(err.Error()) } } return app }
构造函数包含以下步骤:
注意模块的启动方式:顺序很重要!在这里,序列是Auth-> Bank-> Feecollection-> Stake-> Distribution-> Slashing, ,然后为stake模块设置了钩子。这是因为其中一些模块在使用之前就依赖于其他现有模块。
辅助函数:
添加一个辅助函数来生成一个animo–*codec.Codec
,它可以正确地注册你应用程序中使用的所有模块:
1 2 3 4 5 6 7 8 9 10 11 func MakeCodec () *codec.Codec { var cdc = codec.New() ModuleBasics.RegisterCodec(cdc) sdk.RegisterCodec(cdc) codec.RegisterCrypto(cdc) return cdc.Seal() }
现在您已经创建了一个包含模块的应用程序,现在是时候构建入口点了!
程序入口点Entrypoint:
golang的规范是把编译成可执行程序的文件放在项目的./cmd
文件夹中。对于你的应用程序,您要创建2个可执行程序:
相关文件:
cosmos-nameserviced:
相关文件:cmd/cosmos-nameserviced/main.go
注意上述代码中:
cosmos-nameservicecli:
相关文件:cmd/cosmos-nameservicecli/main.go
注意:
代码结合了来自以下包的CLI命令:Tendermint、Cosmos-SDK、你的nameservice模块
cobra CLI文档将有助于理解上述代码
你可以在这里看到之前定义的ModuleClient
注意如何将路由包含在registerRoutes
函数中
现在你已经定义了二进制文件,那么就可以来处理依赖关系管理并构建应用程序。
构建你的程序:
Makefile:
通过在根目录中编写包含常用命令的./Makefile
来帮助用户编译应用程序:
相关文件:./Makefile
go.mod:
Golang有一些依赖管理工具。在本教程中,你将使用Go Modules。Go Modules使用仓库根目录中的go.mod
文件来定义应用程序所需的依赖项。Cosmos SDK 应用程序目前依赖于某些库的特定版本。
编译应用程序:
1 2 3 4 5 6 7 8 9 10 11 vim /etc/profile export PATH=$(go env GOPATH)/bin:$PATH make install cosmos-nameserviced help cosmos-nameservicecli help
cosmos-nameserviced
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 [root@localhost cosmos-nameservice] app Daemon (server) Usage: cosmos-nameserviced [command ] Available Commands: init Initialize private validator, p2p, genesis, and application configuration files collect-gentxs Collect genesis txs and output a genesis.json file migrate Migrate genesis to a specified target version gentx Generate a genesis tx carrying a self delegation validate-genesis validates the genesis file at the default location or at the location passed as an arg add-genesis-account Add a genesis account to genesis.json debug Tool for helping with debugging your application start Run the full node unsafe-reset-all Resets the blockchain database, removes address book files, and resets priv_validator.json to the genesis state tendermint Tendermint subcommands export Export state to JSON version Print the app version help Help about any command Flags: -h, --help help for cosmos-nameserviced --home string directory for config and data (default "/root/.nameserviced" ) --inv-check-period uint Assert registered invariants every N blocks --log_level string Log level (default "main:info,state:info,*:error" ) --trace print out full stack trace on errors Use "cosmos-nameserviced [command] --help" for more information about a command .
cosmos-nameservicecli:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 [root@localhost cosmos-nameservice] Command line interface for interacting with cosmosnameserviced Usage: cosmos-nameservicecli [command ] Available Commands: status Query remote node for status config Create or query an application CLI configuration file query Querying subcommands tx Transactions subcommands rest-server Start LCD (light-client daemon), a local REST server keys Add or view local private keys version Print the app version help Help about any command Flags: --chain-id string Chain ID of tendermint node -e, --encoding string Binary encoding (hex|b64|btc) (default "hex" ) -h, --help help for cosmos-nameservicecli --home string directory for config and data (default "/root/.nameservicecli" ) -o, --output string Output format (text|json) (default "text" ) --trace print out full stack trace on errors Use "cosmos-nameservicecli [command] --help" for more information about a command .
恭喜,您已完成名称服务应用!尝试运行并使用吧!
运行程序:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 cosmos-nameserviced init moniker --chain-id namechain cosmos-nameservicecli keys add jack cosmos-nameservicecli keys add alice cosmos-nameserviced add-genesis-account $(cosmos-nameservicecli keys show jack -a) 1000nametoken,1000000000stake cosmos-nameserviced add-genesis-account $(cosmos-nameservicecli keys show alice -a) 1000nametoken,1000000000stake cosmos-nameservicecli query account $(cosmos-nameservicecli keys show jack -a) cosmos-nameservicecli query account $(cosmos-nameservicecli keys show alice -a) cosmos-nameserviced gentx --name jack cosmos-nameserviced collect-gentxs cosmos-nameserviced validate-genesis cosmos-nameservicecli config chain-id namechain cosmos-nameservicecli config output json cosmos-nameservicecli config indent true cosmos-nameservicecli config trust-node true cosmos-nameserviced start
将看到日志开始不停输出,表示正在生成的区块:
使用cli测试域名解析应用:
1 2 3 cosmos-nameservicecli query account $(cosmos-nameservicecli keys show jack -a) cosmos-nameservicecli query account $(cosmos-nameservicecli keys show alice -a)
购买域名:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 cosmos-nameservicecli tx cosmosnameservice buy-name namechain.com 20nametoken --from jack [root@localhost cosmos-nameservice] Enter keyring passphrase: { "chain_id" : "namechain" , "account_number" : "2" , "sequence" : "1" , "fee" : { "amount" : [], "gas" : "200000" }, "msgs" : [ { "type" : "cosmosnameservice/BuyName" , "value" : { "name" : "namechain.com" , "bid" : [ { "denom" : "nametoken" , "amount" : "20" } ], "buyer" : "cosmos1vwp2kpjyg7g2x8pd320rugw0m7ut8q3ha9m7u4" } } ], "memo" : "" } confirm transaction before signing and broadcasting [y/N]: y Enter keyring passphrase: { "height" : "0" , "txhash" : "5B8C36D6DCF7334255BD8E974D095389D7BC75577024408F4BB7396B607A84F1" , "raw_log" : "[]" }
设置域名解析:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 cosmos-nameservicecli tx cosmosnameservice set-name 8.8.8.8 namechain.com --from jack [root@localhost cosmos-nameservice] Enter keyring passphrase: { "chain_id" : "namechain" , "account_number" : "2" , "sequence" : "4" , "fee" : { "amount" : [], "gas" : "200000" }, "msgs" : [ { "type" : "cosmosnameservice/SetName" , "value" : { "name" : "namechain.com" , "value" : "8.8.8.8" , "owner" : "cosmos1vwp2kpjyg7g2x8pd320rugw0m7ut8q3ha9m7u4" } } ], "memo" : "" } confirm transaction before signing and broadcasting [y/N]: y Enter keyring passphrase: { "height" : "0" , "txhash" : "B9D2D889371BD86AFCCF5C36A2F3143B0FCCF56A7B0B4BDF247301ABBBFA893B" , "raw_log" : "[]" }
查询域名解析:
1 2 3 4 5 6 7 8 cosmos-nameservicecli query cosmosnameservice resolve namechain.com [root@localhost cosmos-nameservice] { "value" : "8.8.8.8" }
whois查询:
1 2 cosmos-nameservicecli query cosmosnameservice get-whois namechain.com
1 2 3 4 5 6 7 8 9 10 11 12 [root@localhost cosmos-nameservice] { "creator" : "cosmos1vwp2kpjyg7g2x8pd320rugw0m7ut8q3ha9m7u4" , "id" : "" , "value" : "8.8.8.8" , "price" : [ { "denom" : "nametoken" , "amount" : "20" } ] }
购买他人的域名:
1 2 cosmos-nameservicecli tx cosmosnameservice buy-name namechain.com 20nametoken --from alice
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 [root@localhost cosmos-nameservice] Enter keyring passphrase: { "chain_id" : "namechain" , "account_number" : "3" , "sequence" : "0" , "fee" : { "amount" : [], "gas" : "200000" }, "msgs" : [ { "type" : "cosmosnameservice/BuyName" , "value" : { "name" : "namechain.com" , "bid" : [ { "denom" : "nametoken" , "amount" : "20" } ], "buyer" : "cosmos192t6nfdqa9z6n3j8djpyupdzjfdwq6mr9v87vw" } } ], "memo" : "" } confirm transaction before signing and broadcasting [y/N]: y Enter keyring passphrase: { "height" : "0" , "txhash" : "F27BB0F6BA6D861B8C8B6FE48F54CC4D2869E313A8F630B0116C8A227C648A61" , "raw_log" : "[]" }
1 2 cosmos-nameservicecli query cosmosnameservice get-whois namechain.com
1 2 3 4 5 6 7 8 9 10 11 12 [root@localhost cosmos-nameservice] { "creator" : "cosmos192t6nfdqa9z6n3j8djpyupdzjfdwq6mr9v87vw" , "id" : "" , "value" : "8.8.8.8" , "price" : [ { "denom" : "nametoken" , "amount" : "20" } ] }
creator已由cosmos1vwp2kpjyg7g2x8pd320rugw0m7ut8q3ha9m7u4
jack变更为cosmos192t6nfdqa9z6n3j8djpyupdzjfdwq6mr9v87vw
alice
删除域名:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 cosmos-nameservicecli tx cosmosnameservice delete-name namechain.com --from alice [root@localhost cosmos-nameservice] Enter keyring passphrase: { "chain_id" : "namechain" , "account_number" : "3" , "sequence" : "1" , "fee" : { "amount" : [], "gas" : "200000" }, "msgs" : [ { "type" : "cosmosnameservice/DeleteName" , "value" : { "id" : "namechain.com" , "creator" : "cosmos192t6nfdqa9z6n3j8djpyupdzjfdwq6mr9v87vw" } } ], "memo" : "" } confirm transaction before signing and broadcasting [y/N]: y Enter keyring passphrase: { "height" : "0" , "txhash" : "D168567D166238B1D11B494BFE13CAC317A72E7D0C296EAAB3F96B817159D156" , "raw_log" : "[]" }
1 2 3 4 [root@localhost cosmos-nameservice] could not resolve whois namechain.com internal
列出所有域名:
1 2 cosmos-nameservicecli query cosmosnameservice list-whois
恭喜,您已经构建了一个Cosmos SDK应用程序!本教程现已完成。接下来是要查看如何使用REST服务器运行相同的命令.
rest部分没测,先略过。
初始化的配置文件:
1 cosmos-nameserviced init <moniker> --chain-id=namechain
初始化配置文件和创世文件,默认目录/root/.nameserviced/config/
生成文件结构如下:
1 2 3 4 5 6 7 8 ├── config │ ├── app.toml │ ├── config.toml │ ├── genesis.json │ ├── node_key.json │ └── priv_validator_key.json └── data └── priv_validator_state.json
app.toml:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 minimum-gas-prices = "" pruning = "default" pruning-keep-recent = "0" pruning-keep-every = "0" pruning-interval = "0" halt-height = 0 halt-time = 0 inter-block-cache = true
config.toml:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 proxy_app = "tcp://127.0.0.1:26658" moniker = "moniker" fast_sync = true db_backend = "goleveldb" db_dir = "data" log_level = "main:info,state:info,*:error" log_format = "plain" genesis_file = "config/genesis.json" priv_validator_key_file = "config/priv_validator_key.json" priv_validator_state_file = "data/priv_validator_state.json" priv_validator_laddr = "" node_key_file = "config/node_key.json" abci = "socket" prof_laddr = "localhost:6060" filter_peers = false laddr = "tcp://127.0.0.1:26657" cors_allowed_origins = [] cors_allowed_methods = ["HEAD" , "GET" , "POST" , ] cors_allowed_headers = ["Origin" , "Accept" , "Content-Type" , "X-Requested-With" , "X-Server-Time" , ] grpc_laddr = "" grpc_max_open_connections = 900 unsafe=false max_open_connections = 900
项目目录:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 ├── app │ ├── app.go │ ├── export .go │ └── prefix.go ├── cmd │ ├── cosmos-nameservicecli │ │ └── main.go │ └── cosmos-nameserviced │ ├── genaccounts.go │ └── main.go ├── config.yml ├── go.mod ├── go.sum ├── Makefile ├── readme.md ├── vue └── x └── cosmosnameservice ├── abci.go ├── client │ ├── cli │ │ ├── query.go │ │ ├── queryWhois.go │ │ ├── tx.go │ │ └── txWhois.go │ └── rest │ ├── queryWhois.go │ ├── rest.go │ └── txWhois.go ├── genesis.go ├── handler.go ├── handlerMsgBuyName.go ├── handlerMsgDeleteName.go ├── handlerMsgSetName.go ├── keeper │ ├── keeper.go │ ├── params.go │ ├── querier.go │ └── whois.go ├── module .go ├── spec │ └── README .md └── types ├── codec.go ├── errors.go ├── events.go ├── expected_keepers.go ├── genesis.go ├── key.go ├── MsgBuyName .go ├── MsgCreateWhois .go ├── MsgDeleteName .go ├── MsgDeleteWhois .go ├── msg.go ├── MsgSetName .go ├── MsgSetWhois .go ├── params.go ├── querier.go ├── types.go └── TypeWhois .go
Refs: