starport 应用案例
存证
编译好starport,开始构建应用案例
构建项目
可以通过运行来构建我们的应用程序starport app github.com/user/pofe
。这将创建一个名为的新文件夹pofe
(Proof of File Existence)
starport app github.com/user/pofe --sdk-version launchpad
运行程序
搭建好应用程序之后,让我们在新创建的pofe
文件夹中打开一个单独的终端窗口,然后运行starport serve
,这将启动我们的应用程序
$ starport serve
Cosmos' version is: Launchpad
📦 Installing dependencies...
🛠️ Building the app...
🙂 Created an account. Password (mnemonic): joy manage firm arrange finish bounce power shove bring bundle issue chief quiz spread symptom sword slender vote hobby water weird trim panther slot
🙂 Created an account. Password (mnemonic): meat skirt slab smart impulse region clump genuine zero clog version father spray midnight tip poem input fantasy decide buddy work strategy nest shaft
🌍 Running a Cosmos 'pofe' app with Tendermint at http://localhost:26657.
🌍 Running a server at http://localhost:1317 (LCD)
🚀 Get started: http://localhost:12345/
创建type类型
在pofe
目录内打开一个新的终端,并创建一个名为claim
field的类型proof
starport type claim proof:string
输出:
🎉 Created a type `claim`.
这将创建claim
类型,并添加其相关的CLI命令,处理程序,消息,类型,查询器和保持器。
至此,我们有了一个正在运行的应用程序-您可以通过检查辅助终端窗口的输出来验证这一点。
修改应用程序
x/pofe/client/cli/txClaim.go
首先,确保添加以下软件包的导入
package cli
import (
...
"crypto/sha256"
"encoding/hex"
"io/ioutil"
)
接下来,我们要更新GetCmdCreateClaim
函数,使其看起来如下所示:
// CLI transaction command to create a claim
func GetCmdCreateClaim(cdc *codec.Codec) *cobra.Command {
return &cobra.Command{
Use: "create-claim [path-to-file]",
Short: "Creates a new claim from a path to a file",
Args: cobra.MinimumNArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
// accept a filepath, read the file, and hash it
hasher := sha256.New()
s, _ := ioutil.ReadFile(args[0])
hasher.Write(s)
argsProof := hex.EncodeToString(hasher.Sum(nil))
// automatically scaffolded by `starport type`
cliCtx := context.NewCLIContext().WithCodec(cdc)
inBuf := bufio.NewReader(cmd.InOrStdin())
txBldr := auth.NewTxBuilderFromCLI(inBuf).WithTxEncoder(utils.GetTxEncoder(cdc))
msg := types.NewMsgCreateClaim(cliCtx.GetFromAddress(), string(argsProof))
err := msg.ValidateBasic()
if err != nil {
return err
}
return utils.GenerateOrBroadcastMsgs(cliCtx, txBldr, []sdk.Msg{msg})
},
}
}
我们可以保持GetCmdSetClaim
和GetCmdDeleteClaim
功能不变。
最后,ID
我们将使用Proof
结构中的值,而不是使用自动生成的uuid作为键。这将使查询Proof
数据库中的事件变得容易得多。我们可以通过修改开始CreateClaim
在我们的方法./x/pofe/keeper/claim.go
文件,以及所有其他相关文件的使用claim.ID
,使用claim.Proof
替代claim.ID
。
x/pofe/keeper/claim.go
func (k Keeper) CreateClaim(ctx sdk.Context, claim types.Claim) {
store := ctx.KVStore(k.storeKey)
key := []byte(types.ClaimPrefix + claim.Proof)
value := k.cdc.MustMarshalBinaryLengthPrefixed(claim)
store.Set(key, value)
}
为了导入types.Kepper声明,我们必须handlerMsgCreateClaim.go
在修改如下
x/pofe/handerMsgCreateClaim.go
确保包引用和业务代码如下
package pofe
import (
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/user/pofe/x/pofe/keeper"
"github.com/user/pofe/x/pofe/types"
)
func handleMsgCreateClaim(ctx sdk.Context, k keeper.Keeper, msg types.MsgCreateClaim) (*sdk.Result, error) {
var claim = types.Claim{
Creator: msg.Creator,
Proof: msg.Proof,
}
k.CreateClaim(ctx, claim)
return &sdk.Result{Events: ctx.EventManager().Events()}, nil
}
提交文件存在证明声明
运行时starport serve
,我们还将应用程序构建为名为的二进制文件pofed
,因此我们可以使用此文件来提交声明:
pofecli tx pofe create-claim $(of pofed)--from user1
确认交易后,运行:
pofecli q pofe list-claim
你可以看到输出如下
[
{
"creator": "cosmos165hphx98d767c99gtm0n7gevq2q0nwrg75pfkd",
"proof": "534f056e58115dd106d026e00da22a32f8c776a0cd5b3dd6431598d73b5f623c"
}
]
如果您要删除或删除声明,则可以通过替换存证证明来简单地运行命令
pofecli tx pofe delete-claim 534f056e58115dd106d026e00da22a32f8c776a0cd5b3dd6431598d73b5f623c --from user1
博客
构建项目
可以通过运行来构建我们的应用程序starport app github.com/example/blog
这将创建一个名为的新文件夹blog
starport app github.com/example/blog
修改应用程序
proto/blog/post.proto
定义proto type类型
syntax = "proto3";
package example.blog.blog;
option go_package = "github.com/example/blog/x/blog/types";
import "gogoproto/gogo.proto";
message Post {
string creator = 1;
string id = 2;
string title = 3;
string body = 4;
}
message MsgCreatePost {
string creator = 1;
string title = 2;
string body = 3;
}
上面的代码定义了帖子的三个属性:创建者,标题,正文和ID。我们为每个帖子生成唯一的全局ID,并将它们存储为字符串。
键值存储中的帖子将如下所示
"post-0": {
"Creator": "cosmos18cd5t4msvp2lpuvh99rwglrmjrrw9qx5h3f3gz",
"Title": "This is a post!",
"Body": "Welcome to my blog app.",
"ID": "0"
},
"post-1": {
...
}
x / blog / client / cli / tx.go
在该import
块中,确保导入以下四个软件包:
import (
"fmt"
"github.com/spf13/cobra"
"github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/client/flags"
"github.com/cosmos/cosmos-sdk/client/tx"
// "github.com/cosmos/cosmos-sdk/client/flags"
"github.com/example/blog/x/blog/types"
)
该文件已包含func GetTxCmd
定义自定义blogd
我们将自定义添加create-post
命令我们blogd
首先加入GetCmdCreatePost
到blogTxCmd
。
cmd.AddCommand(CmdCreatePost())
在文件末尾,让我们定义GetCmdCreatePost
func CmdCreatePost() *cobra.Command {
cmd := &cobra.Command{
Use: "create-post [title] [body]",
Short: "Creates a new post",
Args: cobra.ExactArgs(2),
RunE: func(cmd *cobra.Command, args []string) error {
argsTitle := string(args[0])
argsBody := string(args[1])
clientCtx, err := client.GetClientTxContext(cmd)
if err != nil {
return err
}
msg := types.NewMsgCreatePost(clientCtx.GetFromAddress().String(), string(argsTitle), string(argsBody))
if err := msg.ValidateBasic(); err != nil {
return err
}
return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), msg)
},
}
flags.AddTxFlagsToCmd(cmd)
return cmd
}
上面的函数定义了运行create-post
子命令时发生的情况。create-post
接受两个参数[title] [body]
,创建一条消息NewMsgCreatePost
(标题为args[0]
和args[1]
),并广播此消息在应用程序中进行处理。
x / blog / types / messages_post.go
让我们NewMsgCreatePost
在一个应创建为的新文件中进行定义x/blog/types/messages_post.go
package types
import (
sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
)
var _ sdk.Msg = &MsgCreatePost{}
func NewMsgCreatePost(creator string, title string, body string) *MsgCreatePost {
return &MsgCreatePost{
Creator: creator,
Title: title,
Body: body,
}
}
NewMsgCreatePost
是创建MsgCreatePost
消息的构造函数。必须定义以下五个功能以实现该Msg
接口。它们允许您执行不需要访问商店的验证(例如检查空值)等
// Route ...
func (msg MsgCreatePost) Route() string {
return RouterKey
}
// Type ...
func (msg MsgCreatePost) Type() string {
return "CreatePost"
}
// GetSigners ...
func (msg *MsgCreatePost) GetSigners() []sdk.AccAddress {
creator, err := sdk.AccAddressFromBech32(msg.Creator)
if err != nil {
panic(err)
}
return []sdk.AccAddress{creator}
}
// GetSignBytes ...
func (msg *MsgCreatePost) GetSignBytes() []byte {
bz := ModuleCdc.MustMarshalJSON(msg)
return sdk.MustSortJSON(bz)
}
// ValidateBasic ...
func (msg *MsgCreatePost) ValidateBasic() error {
_, err := sdk.AccAddressFromBech32(msg.Creator)
if err != nil {
return sdkerrors.Wrapf(sdkerrors.ErrInvalidAddress, "invalid creator address (%s)", err)
}
return nil
}
让我们再回到GetCmdCreatePost
中x/blog/client/cli/tx.go
,你会看到MsgCreatePost
正在创建和广播用GenerateOrBroadcastMsgs
x / blog / handler.go
在func NewHandler
定义了列出所有可用处理程序的内容后。对其进行修改以包括一个名为的新函数handleMsgCreatePost
switch msg := msg.(type) {
case *types.MsgCreatePost:
return handleMsgCreatePost(ctx, k, msg)
default:
x / blog / handler_post.go
现在让我们handleMsgCreatePost
在一个新文件中定义handler_post.go
package blog
import (
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/example/blog/x/blog/keeper"
"github.com/example/blog/x/blog/types"
)
func handleMsgCreatePost(ctx sdk.Context, k keeper.Keeper, msg *types.MsgCreatePost) (*sdk.Result, error) {
k.CreatePost(ctx, *msg)
return &sdk.Result{Events: ctx.EventManager().ABCIEvents()}, nil
}
x / blog / keeper / post.go x / blog / keeper / post.go
首先,post.go
在keeper/
目录中创建一个新文件。然后,添加一个带有CreatePost
两个参数的函数,另外,GetPostCount
还有SetPostCount functions
package keeper
import (
"strconv"
"github.com/cosmos/cosmos-sdk/store/prefix"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/example/blog/x/blog/types"
)
// GetPostCount get the total number of post
func (k Keeper) GetPostCount(ctx sdk.Context) int64 {
store := prefix.NewStore(ctx.KVStore(k.storeKey), types.KeyPrefix(types.PostCountKey))
byteKey := types.KeyPrefix(types.PostCountKey)
bz := store.Get(byteKey)
// Count doesn't exist: no element
if bz == nil {
return 0
}
// Parse bytes
count, err := strconv.ParseInt(string(bz), 10, 64)
if err != nil {
// Panic because the count should be always formattable to int64
panic("cannot decode count")
}
return count
}
// SetPostCount set the total number of post
func (k Keeper) SetPostCount(ctx sdk.Context, count int64) {
store := prefix.NewStore(ctx.KVStore(k.storeKey), types.KeyPrefix(types.PostCountKey))
byteKey := types.KeyPrefix(types.PostCountKey)
bz := []byte(strconv.FormatInt(count, 10))
store.Set(byteKey, bz)
}
func (k Keeper) CreatePost(ctx sdk.Context, msg types.MsgCreatePost) {
// Create the post
count := k.GetPostCount(ctx)
var post = types.Post{
Creator: msg.Creator,
Id: strconv.FormatInt(count, 10),
Title: msg.Title,
Body: msg.Body,
}
store := prefix.NewStore(ctx.KVStore(k.storeKey), types.KeyPrefix(types.PostKey))
key := types.KeyPrefix(types.PostKey + post.Id)
value := k.cdc.MustMarshalBinaryBare(&post)
store.Set(key, value)
// Update post count
k.SetPostCount(ctx, count+1)
}
func (k Keeper) GetPost(ctx sdk.Context, key string) types.Post {
store := prefix.NewStore(ctx.KVStore(k.storeKey), types.KeyPrefix(types.PostKey))
var post types.Post
k.cdc.MustUnmarshalBinaryBare(store.Get(types.KeyPrefix(types.PostKey + key)), &post)
return post
}
func (k Keeper) HasPost(ctx sdk.Context, id string) bool {
store := prefix.NewStore(ctx.KVStore(k.storeKey), types.KeyPrefix(types.PostKey))
return store.Has(types.KeyPrefix(types.PostKey + id))
}
func (k Keeper) GetPostOwner(ctx sdk.Context, key string) string {
return k.GetPost(ctx, key).Creator
}
func (k Keeper) GetAllPost(ctx sdk.Context) (msgs []types.Post) {
store := prefix.NewStore(ctx.KVStore(k.storeKey), types.KeyPrefix(types.PostKey))
iterator := sdk.KVStorePrefixIterator(store, types.KeyPrefix(types.PostKey))
defer iterator.Close()
for ; iterator.Valid(); iterator.Next() {
var msg types.Post
k.cdc.MustUnmarshalBinaryBare(iterator.Value(), &msg)
msgs = append(msgs, msg)
}
return
}
CreatePost
通过将发布前缀与ID串联来创建密钥。如果您回顾一下我们商店的外观,您会发现键具有前缀,这就是为什么post-0bae9f7d-20f8-4b51-9d5c-af9103177d66
包含prefix的原因post-
。这样做的原因是您拥有一个商店,但是您可能想要在其中保留不同类型的对象,例如帖子和用户。带post-
和的前缀键user-
使您可以在不同类型的对象之间共享一个存储空间
x / blog / types / keys.go
package types
const (
// Other constants...
// PostPrefix is used for keys in the KV store
PostKey= "Post-value-"
PostCountKey= "Post-count-"
)
x/blog/types/codec.go
package types
import (
"github.com/cosmos/cosmos-sdk/codec"
cdctypes "github.com/cosmos/cosmos-sdk/codec/types"
sdk "github.com/cosmos/cosmos-sdk/types"
)
func RegisterCodec(cdc *codec.LegacyAmino) {
// this line is used by starport scaffolding # 2
cdc.RegisterConcrete(&MsgCreatePost{}, "blog/CreatePost", nil)
}
func RegisterInterfaces(registry cdctypes.InterfaceRegistry) {
// this line is used by starport scaffolding # 3
registry.RegisterImplementations((*sdk.Msg)(nil),
&MsgCreatePost{},
)
}
var (
amino = codec.NewLegacyAmino()
ModuleCdc = codec.NewAminoCodec(amino)
)
运行
starport serve
此命令将安装依赖项,生成并初始化应用程序,并运行服务器
运行以下命令来创建帖子:
blogd tx blog create-post "My first post" "This is a post\!" --from=user1
“这是一个帖子!” 是我们帖子的标题,并--from=user1
告诉程序谁在创建此帖子。user1
是用于签名交易的一对密钥的标签
运行命令并进行确认后,您将看到一个具有“ txhash”属性的对象,其值类似于CA1491B39384A4F29E568F62B156E0F2D0601507EF499CE1B8F3930BAFE7F03C
。
要验证交易是否已处理,请打开浏览器并访问以下URL(确保将其替换CA14...
为txhash的值,但确保具有0x
前缀)
http://localhost:26657/tx?hash=0xCA1491B39384A4F29E568F62B156E0F2D0601507EF499CE1B8F3930BAFE7F03C
拾荒者狩猎游戏
寻宝游戏是关于某人设置任务或问题的方法,这些问题或挑战会挑战参与者以找到附有某种奖励的解决方案。游戏的基本机制如下:
- 任何人都可以发布带有加密答案的问题。
- 这个问题伴随着大量的硬币。
- 任何人都可以发布该问题的答案,如果他们答对了,他们将获得赏金。
这里要注意的是,在处理具有延迟的公共网络时,可能会发生中间人攻击可能发生。攻击者不会假装成为一方,而会从一方获取敏感信息并将其用于自己的利益。这实际上称为前跑并发生如下情况:
- 您发布了某个问题的答案并遭到了赏金攻击。
- 有人看到您发布答案,并在您之前将其自己发布。
- 由于他们首先发布了答案,因此他们获得的不是您而是您的奖励。
为了防止前端运行,我们将实施一个提交发布计划。提交-披露方案转换单个可利用的交互并将其转换为两个安全的交互。
第一个交互是提交。在这里,您“承诺”在后续互动中发布答案。该提交由您的姓名的加密哈希值和您认为正确的答案组成。该应用程序会保存该值,声称您知道答案,但尚未确认答案是否正确。
下一个交互是显示。您可以在此处以纯文本形式发布答案以及您的姓名。该应用程序将获取您的答案和您的姓名,并对其进行加密哈希处理。如果结果与您先前在提交阶段提交的内容相匹配,则将证明实际上知道答案的是您自己,而不是仅仅领先您的人。
这样的系统可以不信任的方式与任何类型的游戏平台一起使用。想象一下,您正在玩《塞尔达传说》,而游戏已经编译好,其中包括对不同寻宝游戏的所有答案。当您达到某个级别时,游戏可能会透露出秘密答案。然后,无论是明确地还是在幕后,此答案都可以与您的名字组合在一起,进行哈希处理,提交并随后显示。您的名字将得到奖励,并且您将在游戏中获得更多积分。
实现此目的的另一种方法是拥有一个访问控制列表,其中有一个由视频游戏公司控制的管理员帐户。该管理员帐户可以确认您是否达到了要求,然后给您积分。问题是它会造成单点故障和一个试图攻击系统的目标。如果有一把钥匙统治着城堡,那么如果该钥匙被盗用,那么整个系统就会被破坏。如果该Admin帐户必须一直在线以使玩家获得积分,这也会造成协调问题。如果您使用提交显示系统,那么您将拥有一个更加不受信任的体系结构,在该体系结构中不需要播放权限。这个设计决定有其优点和缺点,但与精心实施相结合,可以使您的游戏得以扩展,而不会出现瓶颈或故障点
构建项目并运行服务
创建名为scavenge的项目
starport app github.com/github-username/scavenge --sdk-version="launchpad"
运行服务
starport serve
创建type类型
在项目文件夹下打开一个新终端,然后运行以下starport type
命令来生成我们的scavenge
类型
starport type scavenge description solutionHash reward solution scavenger
我们还要创建第二种类型,Commit
以防止前面提到的提交的解决方案在前端运行
starport type commit solutionHash solutionScavengerHash
Messages模块代码修改
Messages types
位于./x/scavenge/types/
目录内。您可以看到该type
命令已经设置了MsgCreateScavenge.go
文件。
在这个新文件中,我们将删除创建清理时不会使用的某些字段
package types
import (
sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
)
var _ sdk.Msg = &MsgCreateScavenge{}
type MsgCreateScavenge struct {
Creator sdk.AccAddress `json:"creator" yaml:"creator"`
Description string `json:"description" yaml:"description"`
SolutionHash string `json:"solutionHash" yaml:"solutionHash"`
Reward sdk.Coins `json:"reward" yaml:"reward"`
}
func NewMsgCreateScavenge(creator sdk.AccAddress, description string, solutionHash string, reward sdk.Coins) MsgCreateScavenge {
return MsgCreateScavenge{
Creator: creator,
Description: description,
SolutionHash: solutionHash,
Reward: reward,
}
}
func (msg MsgCreateScavenge) Route() string {
return RouterKey
}
func (msg MsgCreateScavenge) Type() string {
return "CreateScavenge"
}
func (msg MsgCreateScavenge) GetSigners() []sdk.AccAddress {
return []sdk.AccAddress{sdk.AccAddress(msg.Creator)}
}
func (msg MsgCreateScavenge) GetSignBytes() []byte {
bz := ModuleCdc.MustMarshalJSON(msg)
return sdk.MustSortJSON(bz)
}
func (msg MsgCreateScavenge) ValidateBasic() error {
if msg.Creator.Empty() {
return sdkerrors.Wrap(sdkerrors.ErrInvalidAddress, "creator can't be empty")
}
if msg.SolutionHash == "" {
return sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "solutionHash can't be empty")
}
return nil
}
应用程序中的所有消息都需要遵循该sdk.Msg
界面。消息struct
包含创建新清除时的所有必要信息:
Creator
-谁创造的。这使用sdk.AccAddress
代表由公共密钥cryptograhy控制的应用程序中的帐户的类型。Description
-要解决的问题或对挑战的描述。SolutionHash
-混乱的解决方案。Reward
-这是奖励给谁先提交答案的人。
该Msg
界面还需要设置其他方法,例如,验证的内容struct
以及确认消息是由创建者签名并提交的。
既然可以创建清除方法,那么唯一的其他基本操作就是能够解决它。如前所述,这应分为两个单独的操作:MsgCommitSolution
和MsgRevealSolution
将我们重命名./x/scavenge/types/MsgCreateCommit.go
为./x/scavenge/types/MsgCommitSolution.go
替换函数参数以反映MsgCommitSolution
而不是以前的MsgCreateCommit
描述如何提交解决方案的消息类型应该存在./x/scavenge/types/MsgCommitSolution.go
,如下所示
package types
import (
sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
)
var _ sdk.Msg = &MsgCommitSolution{}
type MsgCommitSolution struct {
Scavenger sdk.AccAddress `json:"scavenger" yaml:"scavenger"` // address of the scavenger
SolutionHash string `json:"solutionhash" yaml:"solutionhash"` // solutionhash of the scavenge
SolutionScavengerHash string `json:"solutionScavengerHash" yaml:"solutionScavengerHash"` // solution hash of the scavenge
}
// NewMsgCommitSolution creates a new MsgCommitSolution instance
func NewMsgCommitSolution(scavenger sdk.AccAddress, solutionHash string, solutionScavengerHash string) MsgCommitSolution {
return MsgCommitSolution{
Scavenger: scavenger,
SolutionHash: solutionHash,
SolutionScavengerHash: solutionScavengerHash,
}
}
func (msg MsgCommitSolution) Route() string {
return RouterKey
}
func (msg MsgCommitSolution) Type() string {
return "CreateCommit"
}
func (msg MsgCommitSolution) GetSigners() []sdk.AccAddress {
return []sdk.AccAddress{sdk.AccAddress(msg.Scavenger)}
}
func (msg MsgCommitSolution) GetSignBytes() []byte {
bz := ModuleCdc.MustMarshalJSON(msg)
return sdk.MustSortJSON(bz)
}
func (msg MsgCommitSolution) ValidateBasic() error {
if msg.Scavenger.Empty() {
return sdkerrors.Wrap(sdkerrors.ErrInvalidAddress, "scavenger can't be empty")
}
return nil
}
消息struct
包含揭示解决方案时的所有必要信息:
Scavenger
-谁在透露解决方案。SolutionHash
-混乱的解决方案(哈希)。SolutionScavengerHash
-这是解决方案和解决方案的人的哈希组合。
该消息也实现了sdk.Msg
接口。
此消息类型应该存在./x/scavenge/types/MsgRevealSolution.go
package types
import (
"crypto/sha256"
"encoding/hex"
"fmt"
sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
)
// MsgRevealSolution
// ------------------------------------------------------------------------------
var _ sdk.Msg = &MsgRevealSolution{}
// MsgRevealSolution - struct for unjailing jailed validator
type MsgRevealSolution struct {
Scavenger sdk.AccAddress `json:"scavenger" yaml:"scavenger"` // address of the scavenger scavenger
SolutionHash string `json:"solutionHash" yaml:"solutionHash"` // SolutionHash of the scavenge
Solution string `json:"solution" yaml:"solution"` // solution of the scavenge
}
// NewMsgRevealSolution creates a new MsgRevealSolution instance
func NewMsgRevealSolution(scavenger sdk.AccAddress, solution string) MsgRevealSolution {
var solutionHash = sha256.Sum256([]byte(solution))
var solutionHashString = hex.EncodeToString(solutionHash[:])
return MsgRevealSolution{
Scavenger: scavenger,
SolutionHash: solutionHashString,
Solution: solution,
}
}
// RevealSolutionConst is RevealSolution Constant
const RevealSolutionConst = "RevealSolution"
// nolint
func (msg MsgRevealSolution) Route() string { return RouterKey }
func (msg MsgRevealSolution) Type() string { return RevealSolutionConst }
func (msg MsgRevealSolution) GetSigners() []sdk.AccAddress {
return []sdk.AccAddress{sdk.AccAddress(msg.Scavenger)}
}
// GetSignBytes gets the bytes for the message signer to sign on
func (msg MsgRevealSolution) GetSignBytes() []byte {
bz := ModuleCdc.MustMarshalJSON(msg)
return sdk.MustSortJSON(bz)
}
// ValidateBasic validity check for the AnteHandler
func (msg MsgRevealSolution) ValidateBasic() error {
if msg.Scavenger.Empty() {
return sdkerrors.Wrap(sdkerrors.ErrInvalidAddress, "scavenger can't be empty")
}
if msg.SolutionHash == "" {
return sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "solutionScavengerHash can't be empty")
}
if msg.Solution == "" {
return sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "solutionHash can't be empty")
}
var solutionHash = sha256.Sum256([]byte(msg.Solution))
var solutionHashString = hex.EncodeToString(solutionHash[:])
if msg.SolutionHash != solutionHashString {
return sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, fmt.Sprintf("Hash of solution (%s) doesn't equal solutionHash (%s)", msg.SolutionHash, solutionHashString))
}
return nil
}
消息struct
包含揭示解决方案时的所有必要信息:
Scavenger
-谁在透露解决方案。SolutionHash
-混乱的解决方案。Solution
-解决方案的纯文本版本。
该消息也实现了sdk.Msg
接口。
特别是看ValidateBasic
功能。它验证是否进行了所有必要的输入以显示解决方案,并从提交的解决方案中创建了sha256哈希。
MsgSetScavenge
package types
import (
sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
)
var _ sdk.Msg = &MsgSetScavenge{}
type MsgSetScavenge struct {
Creator sdk.AccAddress `json:"creator" yaml:"creator"`
Description string `json:"description" yaml:"description"`
SolutionHash string `json:"solutionHash" yaml:"solutionHash"`
Reward string `json:"reward" yaml:"reward"`
Solution string `json:"solution" yaml:"solution"`
Scavenger sdk.AccAddress `json:"scavenger" yaml:"scavenger"`
}
func NewMsgSetScavenge(creator sdk.AccAddress, description string, solutionHash string, reward string, solution string, scavenger sdk.AccAddress) MsgSetScavenge {
return MsgSetScavenge{
Creator: creator,
Description: description,
SolutionHash: solutionHash,
Reward: reward,
Solution: solution,
Scavenger: scavenger,
}
}
func (msg MsgSetScavenge) Route() string {
return RouterKey
}
func (msg MsgSetScavenge) Type() string {
return "SetScavenge"
}
func (msg MsgSetScavenge) GetSigners() []sdk.AccAddress {
return []sdk.AccAddress{sdk.AccAddress(msg.Creator)}
}
func (msg MsgSetScavenge) GetSignBytes() []byte {
bz := ModuleCdc.MustMarshalJSON(msg)
return sdk.MustSortJSON(bz)
}
func (msg MsgSetScavenge) ValidateBasic() error {
if msg.Creator.Empty() {
return sdkerrors.Wrap(sdkerrors.ErrInvalidAddress, "creator can't be empty")
}
return nil
}
MsgDeleteScavenge
package types
import (
sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
)
var _ sdk.Msg = &MsgDeleteScavenge{}
type MsgDeleteScavenge struct {
ID string `json:"id" yaml:"id"`
Creator sdk.AccAddress `json:"creator" yaml:"creator"`
}
func NewMsgDeleteScavenge(id string, creator sdk.AccAddress) MsgDeleteScavenge {
return MsgDeleteScavenge{
ID: id,
Creator: creator,
}
}
func (msg MsgDeleteScavenge) Route() string {
return RouterKey
}
func (msg MsgDeleteScavenge) Type() string {
return "DeleteScavenge"
}
func (msg MsgDeleteScavenge) GetSigners() []sdk.AccAddress {
return []sdk.AccAddress{sdk.AccAddress(msg.Creator)}
}
func (msg MsgDeleteScavenge) GetSignBytes() []byte {
bz := ModuleCdc.MustMarshalJSON(msg)
return sdk.MustSortJSON(bz)
}
func (msg MsgDeleteScavenge) ValidateBasic() error {
if msg.Creator.Empty() {
return sdkerrors.Wrap(sdkerrors.ErrInvalidAddress, "creator can't be empty")
}
return nil
}
MsgSetCommit
package types
import (
sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
)
var _ sdk.Msg = &MsgSetCommit{}
type MsgSetCommit struct {
Scavenger sdk.AccAddress `json:"scavenger" yaml:"scavenger"`
SolutionHash string `json:"solutionHash" yaml:"solutionHash"`
SolutionScavengerHash string `json:"solutionScavengerHash" yaml:"solutionScavengerHash"`
}
func NewMsgSetCommit(scavenger sdk.AccAddress, id string, solutionHash string, solutionScavengerHash string) MsgSetCommit {
return MsgSetCommit{
Scavenger: scavenger,
SolutionHash: solutionHash,
SolutionScavengerHash: solutionScavengerHash,
}
}
func (msg MsgSetCommit) Route() string {
return RouterKey
}
func (msg MsgSetCommit) Type() string {
return "SetCommit"
}
func (msg MsgSetCommit) GetSigners() []sdk.AccAddress {
return []sdk.AccAddress{sdk.AccAddress(msg.Scavenger)}
}
func (msg MsgSetCommit) GetSignBytes() []byte {
bz := ModuleCdc.MustMarshalJSON(msg)
return sdk.MustSortJSON(bz)
}
func (msg MsgSetCommit) ValidateBasic() error {
if msg.Scavenger.Empty() {
return sdkerrors.Wrap(sdkerrors.ErrInvalidAddress, "scavenger can't be empty")
}
return nil
}
MsgDeleteCommit
package types
import (
sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
)
var _ sdk.Msg = &MsgDeleteCommit{}
type MsgDeleteCommit struct {
ID string `json:"id" yaml:"id"`
Creator sdk.AccAddress `json:"creator" yaml:"creator"`
}
func NewMsgDeleteCommit(id string, creator sdk.AccAddress) MsgDeleteCommit {
return MsgDeleteCommit{
ID: id,
Creator: creator,
}
}
func (msg MsgDeleteCommit) Route() string {
return RouterKey
}
func (msg MsgDeleteCommit) Type() string {
return "DeleteCommit"
}
func (msg MsgDeleteCommit) GetSigners() []sdk.AccAddress {
return []sdk.AccAddress{sdk.AccAddress(msg.Creator)}
}
func (msg MsgDeleteCommit) GetSignBytes() []byte {
bz := ModuleCdc.MustMarshalJSON(msg)
return sdk.MustSortJSON(bz)
}
func (msg MsgDeleteCommit) ValidateBasic() error {
if msg.Creator.Empty() {
return sdkerrors.Wrap(sdkerrors.ErrInvalidAddress, "creator can't be empty")
}
return nil
}
编解码器
定义消息后,我们需要向编码器描述如何将其存储为字节。为此,我们编辑位于的文件./x/scavenge/types/codec.go
。通过如下描述我们的类型,它们将与我们的编码库一起使用
package types
import (
"github.com/cosmos/cosmos-sdk/codec"
)
// RegisterCodec registers concrete types on codec
func RegisterCodec(cdc *codec.Codec) {
// this line is used by starport scaffolding # 1
cdc.RegisterConcrete(MsgCommitSolution{}, "scavenge/CreateCommit", nil)
cdc.RegisterConcrete(MsgSetCommit{}, "scavenge/SetCommit", nil)
cdc.RegisterConcrete(MsgDeleteCommit{}, "scavenge/DeleteCommit", nil)
cdc.RegisterConcrete(MsgCreateScavenge{}, "scavenge/CreateScavenge", nil)
cdc.RegisterConcrete(MsgSetScavenge{}, "scavenge/SetScavenge", nil)
cdc.RegisterConcrete(MsgDeleteScavenge{}, "scavenge/DeleteScavenge", nil)
cdc.RegisterConcrete(MsgRevealSolution{}, "scavenge/MsgRevealSolution", nil)
}
// ModuleCdc defines the module codec
var ModuleCdc *codec.Codec
func init() {
ModuleCdc = codec.New()
RegisterCodec(ModuleCdc)
codec.RegisterCrypto(ModuleCdc)
ModuleCdc.Seal()
}
Keep模块代码修改
使用该starport
命令后,您应该Keeper
在处有一个样板./x/scavenge/keeper/keeper.go
。它包含了像基本功能引用一个基本的门将Set
,Get
和Delete
。
管理员将所有数据存储在模块中。有时一个模块会导入另一个模块的管理器。这将允许在模块之间共享和修改状态。由于我们在处理模块中的coin作为赏金奖励,因此我们需要访问bank
模块的管理员(我们称之为CoinKeeper
)。看看我们完成的Keeper
文件,你可以看到那里的bank
门将被引用,以及如何Set
,Get
以及Delete
展开
keeper/keeper.go
package keeper
import (
"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/github-username/scavenge/x/scavenge/types"
)
// Keeper of the scavenge store
type Keeper struct {
CoinKeeper bank.Keeper
storeKey sdk.StoreKey
cdc *codec.Codec
// paramspace types.ParamSubspace
}
// NewKeeper creates a scavenge keeper
func NewKeeper(coinKeeper bank.Keeper, cdc *codec.Codec, key sdk.StoreKey) Keeper {
keeper := Keeper{
CoinKeeper: coinKeeper,
storeKey: key,
cdc: cdc,
// paramspace: paramspace.WithKeyTable(types.ParamKeyTable()),
}
return keeper
}
// Logger returns a module-specific logger.
func (k Keeper) Logger(ctx sdk.Context) log.Logger {
return ctx.Logger().With("module", fmt.Sprintf("x/%s", types.ModuleName))
}
// Get returns the pubkey from the adddress-pubkey relation
// func (k Keeper) Get(ctx sdk.Context, key string) (/* TODO: Fill out this type */, error) {
// store := ctx.KVStore(k.storeKey)
// var item /* TODO: Fill out this type */
// byteKey := []byte(key)
// err := k.cdc.UnmarshalBinaryLengthPrefixed(store.Get(byteKey), &item)
// if err != nil {
// return nil, err
// }
// return item, nil
// }
// func (k Keeper) set(ctx sdk.Context, key string, value /* TODO: fill out this type */ ) {
// store := ctx.KVStore(k.storeKey)
// bz := k.cdc.MustMarshalBinaryLengthPrefixed(value)
// store.Set([]byte(key), bz)
// }
// func (k Keeper) delete(ctx sdk.Context, key string) {
// store := ctx.KVStore(k.storeKey)
// store.Delete([]byte(key))
// }
keeper/scavenge.go
package keeper
import (
"strconv"
sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
"github.com/cosmos/cosmos-sdk/codec"
"github.com/github-username/scavenge/x/scavenge/types"
)
// GetScavengeCount get the total number of scavenge
func (k Keeper) GetScavengeCount(ctx sdk.Context) int64 {
store := ctx.KVStore(k.storeKey)
byteKey := []byte(types.ScavengeCountPrefix)
bz := store.Get(byteKey)
// Count doesn't exist: no element
if bz == nil {
return 0
}
// Parse bytes
count, err := strconv.ParseInt(string(bz), 10, 64)
if err != nil {
// Panic because the count should be always formattable to int64
panic("cannot decode count")
}
return count
}
// SetScavengeCount set the total number of scavenge
func (k Keeper) SetScavengeCount(ctx sdk.Context, count int64) {
store := ctx.KVStore(k.storeKey)
byteKey := []byte(types.ScavengeCountPrefix)
bz := []byte(strconv.FormatInt(count, 10))
store.Set(byteKey, bz)
}
// CreateScavenge creates a scavenge
func (k Keeper) CreateScavenge(ctx sdk.Context, scavenge types.Scavenge) {
store := ctx.KVStore(k.storeKey)
key := []byte(types.ScavengePrefix + scavenge.SolutionHash)
value := k.cdc.MustMarshalBinaryLengthPrefixed(scavenge)
store.Set(key, value)
}
// GetScavenge returns the scavenge information
func (k Keeper) GetScavenge(ctx sdk.Context, solutionHash string) (types.Scavenge, error) {
store := ctx.KVStore(k.storeKey)
var scavenge types.Scavenge
byteKey := []byte(types.ScavengePrefix + solutionHash)
err := k.cdc.UnmarshalBinaryLengthPrefixed(store.Get(byteKey), &scavenge)
if err != nil {
return scavenge, err
}
return scavenge, nil
}
// SetScavenge sets a scavenge
func (k Keeper) SetScavenge(ctx sdk.Context, scavenge types.Scavenge) {
solutionHash := scavenge.SolutionHash
store := ctx.KVStore(k.storeKey)
bz := k.cdc.MustMarshalBinaryLengthPrefixed(scavenge)
key := []byte(types.ScavengePrefix + solutionHash)
// You can perform additional checks here, depending on your use case
store.Set(key, bz)
}
// DeleteScavenge deletes a scavenge
func (k Keeper) DeleteScavenge(ctx sdk.Context, solutionHash string) {
store := ctx.KVStore(k.storeKey)
store.Delete([]byte(types.ScavengePrefix + solutionHash))
}
//
// Functions used by querier
//
func listScavenge(ctx sdk.Context, k Keeper) ([]byte, error) {
var scavengeList []types.Scavenge
store := ctx.KVStore(k.storeKey)
iterator := sdk.KVStorePrefixIterator(store, []byte(types.ScavengePrefix))
for ; iterator.Valid(); iterator.Next() {
var scavenge types.Scavenge
k.cdc.MustUnmarshalBinaryLengthPrefixed(store.Get(iterator.Key()), &scavenge)
scavengeList = append(scavengeList, scavenge)
}
res := codec.MustMarshalJSONIndent(k.cdc, scavengeList)
return res, nil
}
func getScavenge(ctx sdk.Context, path []string, k Keeper) (res []byte, sdkError error) {
solutionHash := path[0]
scavenge, err := k.GetScavenge(ctx, solutionHash)
if err != nil {
return nil, err
}
res, err = codec.MarshalJSONIndent(k.cdc, scavenge)
if err != nil {
return nil, sdkerrors.Wrap(sdkerrors.ErrJSONMarshal, err.Error())
}
return res, nil
}
// Get creator of the item
func (k Keeper) GetScavengeOwner(ctx sdk.Context, key string) sdk.AccAddress {
scavenge, err := k.GetScavenge(ctx, key)
if err != nil {
return nil
}
return scavenge.Creator
}
// Check if the key exists in the store
func (k Keeper) ScavengeExists(ctx sdk.Context, key string) bool {
store := ctx.KVStore(k.storeKey)
return store.Has([]byte(types.ScavengePrefix + key))
}
keeper/commit.go
package keeper
import (
"strconv"
sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
"github.com/cosmos/cosmos-sdk/codec"
"github.com/github-username/scavenge/x/scavenge/types"
)
// GetCommitCount get the total number of commit
func (k Keeper) GetCommitCount(ctx sdk.Context) int64 {
store := ctx.KVStore(k.storeKey)
byteKey := []byte(types.CommitCountPrefix)
bz := store.Get(byteKey)
// Count doesn't exist: no element
if bz == nil {
return 0
}
// Parse bytes
count, err := strconv.ParseInt(string(bz), 10, 64)
if err != nil {
// Panic because the count should be always formattable to int64
panic("cannot decode count")
}
return count
}
// SetCommitCount set the total number of commit
func (k Keeper) SetCommitCount(ctx sdk.Context, count int64) {
store := ctx.KVStore(k.storeKey)
byteKey := []byte(types.CommitCountPrefix)
bz := []byte(strconv.FormatInt(count, 10))
store.Set(byteKey, bz)
}
// CreateCommit creates a commit
func (k Keeper) CreateCommit(ctx sdk.Context, commit types.Commit) {
store := ctx.KVStore(k.storeKey)
key := []byte(types.CommitPrefix + commit.SolutionScavengerHash)
value := k.cdc.MustMarshalBinaryLengthPrefixed(commit)
store.Set(key, value)
}
func (k Keeper) GetCommit(ctx sdk.Context, key string) (types.Commit, error) {
store := ctx.KVStore(k.storeKey)
var commit types.Commit
byteKey := []byte(types.CommitPrefix + key)
err := k.cdc.UnmarshalBinaryLengthPrefixed(store.Get(byteKey), &commit)
if err != nil {
return commit, err
}
return commit, nil
}
// SetCommit sets a commit
func (k Keeper) SetCommit(ctx sdk.Context, commit types.Commit) {
commitKey := commit.SolutionHash
store := ctx.KVStore(k.storeKey)
bz := k.cdc.MustMarshalBinaryLengthPrefixed(commit)
key := []byte(types.CommitPrefix + commitKey)
store.Set(key, bz)
}
// DeleteCommit deletes a commit
func (k Keeper) DeleteCommit(ctx sdk.Context, key string) {
store := ctx.KVStore(k.storeKey)
store.Delete([]byte(types.CommitPrefix + key))
}
//
// Functions used by querier
//
func getCommit(ctx sdk.Context, path []string, k Keeper) (res []byte, sdkError error) {
key := path[0]
commit, err := k.GetCommit(ctx, key)
if err != nil {
return nil, err
}
res, err = codec.MarshalJSONIndent(k.cdc, commit)
if err != nil {
return nil, sdkerrors.Wrap(sdkerrors.ErrJSONMarshal, err.Error())
}
return res, nil
}
func listCommit(ctx sdk.Context, k Keeper) ([]byte, error) {
var commitList []types.Commit
store := ctx.KVStore(k.storeKey)
iterator := sdk.KVStorePrefixIterator(store, []byte(types.CommitPrefix))
for ; iterator.Valid(); iterator.Next() {
var commit types.Commit
k.cdc.MustUnmarshalBinaryLengthPrefixed(store.Get(iterator.Key()), &commit)
commitList = append(commitList, commit)
}
res := codec.MustMarshalJSONIndent(k.cdc, commitList)
return res, nil
}
// Get creator of the item
func (k Keeper) GetCommitOwner(ctx sdk.Context, key string) sdk.AccAddress {
commit, err := k.GetCommit(ctx, key)
if err != nil {
return nil
}
return commit.Scavenger
}
// Check if the key exists in the store
func (k Keeper) CommitExists(ctx sdk.Context, key string) bool {
store := ctx.KVStore(k.storeKey)
return store.Has([]byte(types.CommitPrefix + key))
}
Commits and Scavenges
您可能会注意到types.Commit
并types.Scavenge
贯穿了整个参考Keeper
。这些是定义的新结构,./x/scavenge/types/type<Type>.go
其中包含有关不同清除挑战和针对这些挑战的不同已提交解决方案的所有必要信息。它们看起来类似于Msg
我们之前看到的类型,因为它们包含相似的信息。我们将对支架文件进行一些修改。
在TypeScavenge.go
文件中,我们需要删除该ID
字段,因为我们将使用SolutionHash
键作为键。我们还需要更新Reward
到sdk.Coins
,以及Scavenger
到sdk.AccAddress
,所以我们可以支付一次扫解决。
完成此操作后,您的结构scavenge/x/scavenge/types/TypeScavenge.go
应如下
package types
import (
sdk "github.com/cosmos/cosmos-sdk/types"
)
type Scavenge struct {
Creator sdk.AccAddress `json:"creator" yaml:"creator"`
Description string `json:"description" yaml:"description"`
SolutionHash string `json:"solutionHash" yaml:"solutionHash"`
Reward sdk.Coins `json:"reward" yaml:"reward"`
Solution string `json:"solution" yaml:"solution"`
Scavenger sdk.AccAddress `json:"scavenger" yaml:"scavenger"`
}
为此TypeCommit.go
,我们需要删除该ID
字段,然后将该Creator
字段重命名为Scavenger
package types
import (
sdk "github.com/cosmos/cosmos-sdk/types"
)
type Commit struct {
Scavenger sdk.AccAddress `json:"scavenger" yaml:"scavenger"`
SolutionHash string `json:"solutionHash" yaml:"solutionHash"`
SolutionScavengerHash string `json:"solutionScavengerHash" yaml:"solutionScavengerHash"`
}
您可以想象,未解决的字段Scavenge
将包含nil
字段的值,Solution
然后Scavenger
再求解。您可能还注意到每种类型都有该String
方法。这有助于将结构呈现为字符串
Prefixes
您可能会注意到的使用types.ScavengePrefix
,types.ScavengeCountPrefix
以及types.CommitPrefix
或types.CommitCountPrefix
。这些定义在一个名为的文件中./x/scavenge/types/key.go
,可帮助我们保持Keeper
组织良好。该Keeper
实际上只是一个键值存储。这意味着,与Object
javascript中的相似,所有值都在键下引用。要访问值,您需要知道存储它的键。这有点像唯一标识符(UID)。
在存储a时,Scavenge
我们使用的密钥SolutionHash
作为唯一ID,对于a时,Commit
我们使用的密钥SolutionScavengeHash
。但是,由于我们将这两种数据类型存储在同一位置,因此我们可能希望区分用作键的哈希类型。我们可以通过在散列上添加前缀来做到这一点,从而使我们能够识别出哪一个。因为Scavenge
我们看到了前缀scavenge-value
和scavenge-count
,所以Commit
我们看到了前缀commit-value
和commit-count
。
您应该在key.go
文件中看到这些内容,因此如下所示
package types
const (
// ModuleName is the name of the module
ModuleName = "scavenge"
// StoreKey to be used when creating the KVStore
StoreKey = ModuleName
// RouterKey to be used for routing msgs
RouterKey = ModuleName
// QuerierRoute to be used for querier msgs
QuerierRoute = ModuleName
)
const (
ScavengePrefix = "scavenge-value-"
ScavengeCountPrefix = "scavenge-count-"
)
const (
CommitPrefix = "commit-value-"
CommitCountPrefix = "commit-count-"
)
迭代器
有时,您可能想直接通过其键访问aCommit
或a Scavenge
。这就是为什么我们有方法GetCommit
和的原因GetScavenge
。
但是,有时您会想要Scavenge
一次或一次获取所有内容Commit
。为此,我们使用称为的迭代器KVStorePrefixIterator
。此实用程序来自,cosmos sdk
并在密钥存储上进行迭代。如果提供前缀,它将仅对包含该前缀的键进行迭代。由于我们为Scavenge
和我们定义了前缀,因此Commit
我们可以在此处使用它们以仅返回所需的数据类型。
Handler模块代码修改
为了使消息到达Keeper,它必须经过Handler。在这里可以应用逻辑来允许或拒绝aMessage
成功。这也是逻辑准确显示状态更改应如何在Keeper中进行的地方。如果您熟悉Model View Controller(MVC)架构,Keeper
有点像Model,Handler
有点像Controller。如果您熟悉React / Redux 或Vue / Vuex架构,Keeper
有点像Reducer / Store,而Handler
有点像Actions。
我们的处理程序将进入./x/scavenge/handler.go
并遵循样板中列出的建议。我们将创建一个名为单独的文件处理功能,handler<Action.go
为我们的每一个三种Message
类型MsgCreateScavenge
,MsgCommitSolution
和MsgRevealSolution
。
运行starport type
命令应该已经添加了handlerMsgCreateScavenge.go
和handlerMsgCreateCommit.go
文件。本质上,您可以重命名handlerMsgCreateCommit
为handlerMsgCommitSolution
。制作一份副本并将其用作的模板handlerMsgRevealSolution
。我们将修改文件如下所示
handlerMsgCreateScavenge.go
package scavenge
import (
sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
"github.com/tendermint/tendermint/crypto"
"github.com/github-username/scavenge/x/scavenge/keeper"
"github.com/github-username/scavenge/x/scavenge/types"
)
func handleMsgCreateScavenge(ctx sdk.Context, k keeper.Keeper, msg types.MsgCreateScavenge) (*sdk.Result, error) {
var scavenge = types.Scavenge{
Creator: msg.Creator,
Description: msg.Description,
SolutionHash: msg.SolutionHash,
Reward: msg.Reward,
}
// Ensure no scavenge exists
_, err := k.GetScavenge(ctx, scavenge.SolutionHash)
if err == nil {
return nil, sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "Scavenge with that solution hash already exists")
}
moduleAcct := sdk.AccAddress(crypto.AddressHash([]byte(types.ModuleName)))
sdkError := k.CoinKeeper.SendCoins(ctx, scavenge.Creator, moduleAcct, scavenge.Reward)
if sdkError != nil {
return nil, sdkError
}
k.CreateScavenge(ctx, scavenge)
ctx.EventManager().EmitEvent(
sdk.NewEvent(
sdk.EventTypeMessage,
sdk.NewAttribute(sdk.AttributeKeyModule, types.AttributeValueCategory),
sdk.NewAttribute(sdk.AttributeKeyAction, types.EventTypeCreateScavenge),
sdk.NewAttribute(sdk.AttributeKeySender, msg.Creator.String()),
sdk.NewAttribute(types.AttributeDescription, msg.Description),
sdk.NewAttribute(types.AttributeSolutionHash, msg.SolutionHash),
sdk.NewAttribute(types.AttributeReward, msg.Reward.String()),
),
)
return &sdk.Result{Events: ctx.EventManager().Events()}, nil
}
handlerMsgSetScavenge.go
package scavenge
import (
sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
"github.com/github-username/scavenge/x/scavenge/keeper"
"github.com/github-username/scavenge/x/scavenge/types"
)
func handleMsgSetScavenge(ctx sdk.Context, k keeper.Keeper, msg types.MsgSetScavenge) (*sdk.Result, error) {
var scavenge = types.Scavenge{
Creator: msg.Creator,
Description: msg.Description,
SolutionHash: msg.SolutionHash,
Solution: msg.Solution,
}
if !msg.Creator.Equals(k.GetScavengeOwner(ctx, msg.SolutionHash)) { // Checks if the the msg sender is the same as the current owner
return nil, sdkerrors.Wrap(sdkerrors.ErrUnauthorized, "Incorrect Owner") // If not, throw an error
}
k.SetScavenge(ctx, scavenge)
return &sdk.Result{Events: ctx.EventManager().Events()}, nil
}
handlerMsgDeleteScavenge.go
package scavenge
import (
sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
"github.com/github-username/scavenge/x/scavenge/types"
"github.com/github-username/scavenge/x/scavenge/keeper"
)
// Handle a message to delete name
func handleMsgDeleteScavenge(ctx sdk.Context, k keeper.Keeper, msg types.MsgDeleteScavenge) (*sdk.Result, error) {
if !k.ScavengeExists(ctx, msg.ID) {
// replace with ErrKeyNotFound for 0.39+
return nil, sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, msg.ID)
}
if !msg.Creator.Equals(k.GetScavengeOwner(ctx, msg.ID)) {
return nil, sdkerrors.Wrap(sdkerrors.ErrUnauthorized, "Incorrect Owner")
}
k.DeleteScavenge(ctx, msg.ID)
return &sdk.Result{}, nil
}
handlerMsgCommitSolution.go
重命名handlerMsgCreateCommit.go
到 handlerMsgCommitSolution.go
package scavenge
import (
sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
"github.com/github-username/scavenge/x/scavenge/keeper"
"github.com/github-username/scavenge/x/scavenge/types"
)
func handleMsgCommitSolution(ctx sdk.Context, k keeper.Keeper, msg types.MsgCommitSolution) (*sdk.Result, error) {
var commit = types.Commit{
Scavenger: msg.Scavenger,
SolutionHash: msg.SolutionHash,
SolutionScavengerHash: msg.SolutionScavengerHash,
}
_, err := k.GetCommit(ctx, commit.SolutionScavengerHash)
// should produce an error when commit is not found
if err == nil {
return nil, sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "Commit with that hash already exists")
}
k.CreateCommit(ctx, commit)
ctx.EventManager().EmitEvent(
sdk.NewEvent(
sdk.EventTypeMessage,
sdk.NewAttribute(sdk.AttributeKeyModule, types.AttributeValueCategory),
sdk.NewAttribute(sdk.AttributeKeyAction, types.EventTypeCommitSolution),
sdk.NewAttribute(sdk.AttributeKeySender, msg.Scavenger.String()),
sdk.NewAttribute(types.AttributeSolutionHash, msg.SolutionHash),
sdk.NewAttribute(types.AttributeSolutionScavengerHash, msg.SolutionScavengerHash),
),
)
return &sdk.Result{Events: ctx.EventManager().Events()}, nil
}
handlerMsgSetCommit.go
package scavenge
import (
sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
"github.com/github-username/scavenge/x/scavenge/keeper"
"github.com/github-username/scavenge/x/scavenge/types"
)
func handleMsgSetCommit(ctx sdk.Context, k keeper.Keeper, msg types.MsgSetCommit) (*sdk.Result, error) {
var commit = types.Commit{
Scavenger: msg.Scavenger,
SolutionHash: msg.SolutionHash,
SolutionScavengerHash: msg.SolutionScavengerHash,
}
if !msg.Scavenger.Equals(k.GetCommitOwner(ctx, msg.SolutionHash)) { // Checks if the the msg sender is the same as the current owner
return nil, sdkerrors.Wrap(sdkerrors.ErrUnauthorized, "Incorrect Owner") // If not, throw an error
}
k.SetCommit(ctx, commit)
return &sdk.Result{Events: ctx.EventManager().Events()}, nil
}
handlerMsgDeleteCommit.go
package scavenge
import (
sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
"github.com/github-username/scavenge/x/scavenge/types"
"github.com/github-username/scavenge/x/scavenge/keeper"
)
// Handle a message to delete name
func handleMsgDeleteCommit(ctx sdk.Context, k keeper.Keeper, msg types.MsgDeleteCommit) (*sdk.Result, error) {
if !k.CommitExists(ctx, msg.ID) {
// replace with ErrKeyNotFound for 0.39+
return nil, sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, msg.ID)
}
if !msg.Creator.Equals(k.GetCommitOwner(ctx, msg.ID)) {
return nil, sdkerrors.Wrap(sdkerrors.ErrUnauthorized, "Incorrect Owner")
}
k.DeleteCommit(ctx, msg.ID)
return &sdk.Result{}, nil
}
handlerMsgRevealSolution.go
package scavenge
import (
"crypto/sha256"
"encoding/hex"
sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
"github.com/tendermint/tendermint/crypto"
"github.com/github-username/scavenge/x/scavenge/keeper"
"github.com/github-username/scavenge/x/scavenge/types"
)
func handleMsgRevealSolution(ctx sdk.Context, k keeper.Keeper, msg types.MsgRevealSolution) (*sdk.Result, error) {
var solutionScavengerBytes = []byte(msg.Solution + msg.Scavenger.String())
var solutionScavengerHash = sha256.Sum256(solutionScavengerBytes)
var solutionScavengerHashString = hex.EncodeToString(solutionScavengerHash[:])
_, err := k.GetCommit(ctx, solutionScavengerHashString)
if err != nil {
return nil, sdkerrors.Wrap(err, "Commit with that hash doesn't exists")
}
var solutionHash = sha256.Sum256([]byte(msg.Solution))
var solutionHashString = hex.EncodeToString(solutionHash[:])
var scavenge types.Scavenge
scavenge, err = k.GetScavenge(ctx, solutionHashString)
if err != nil {
return nil, sdkerrors.Wrap(err, "Scavenge with that solution hash doesn't exists")
}
if scavenge.Scavenger != nil {
return nil, sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "Scavenge has already been solved")
}
scavenge.Scavenger = msg.Scavenger
scavenge.Solution = msg.Solution
moduleAcct := sdk.AccAddress(crypto.AddressHash([]byte(types.ModuleName)))
sdkError := k.CoinKeeper.SendCoins(ctx, moduleAcct, scavenge.Scavenger, scavenge.Reward)
if sdkError != nil {
return nil, sdkError
}
k.SetScavenge(ctx, scavenge)
ctx.EventManager().EmitEvent(
sdk.NewEvent(
sdk.EventTypeMessage,
sdk.NewAttribute(sdk.AttributeKeyModule, types.AttributeValueCategory),
sdk.NewAttribute(sdk.AttributeKeyAction, types.EventTypeSolveScavenge),
sdk.NewAttribute(sdk.AttributeKeySender, msg.Scavenger.String()),
sdk.NewAttribute(types.AttributeSolutionHash, solutionHashString),
sdk.NewAttribute(types.AttributeDescription, scavenge.Description),
sdk.NewAttribute(types.AttributeSolution, msg.Solution),
sdk.NewAttribute(types.AttributeScavenger, scavenge.Scavenger.String()),
sdk.NewAttribute(types.AttributeReward, scavenge.Reward.String()),
),
)
return &sdk.Result{Events: ctx.EventManager().Events()}, nil
}
完成后,我们需要在主处理程序中注册以下功能
handler.go
package scavenge
import (
"fmt"
sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
"github.com/github-username/scavenge/x/scavenge/keeper"
"github.com/github-username/scavenge/x/scavenge/types"
)
// NewHandler ...
func NewHandler(k keeper.Keeper) sdk.Handler {
return func(ctx sdk.Context, msg sdk.Msg) (*sdk.Result, error) {
ctx = ctx.WithEventManager(sdk.NewEventManager())
switch msg := msg.(type) {
// this line is used by starport scaffolding # 1
case types.MsgSetCommit:
return handleMsgSetCommit(ctx, k, msg)
case types.MsgDeleteCommit:
return handleMsgDeleteCommit(ctx, k, msg)
case types.MsgCommitSolution:
return handleMsgCommitSolution(ctx, k, msg)
case types.MsgCreateScavenge:
return handleMsgCreateScavenge(ctx, k, msg)
case types.MsgRevealSolution:
return handleMsgRevealSolution(ctx, k, msg)
case types.MsgSetScavenge:
return handleMsgSetScavenge(ctx, k, msg)
case types.MsgDeleteScavenge:
return handleMsgDeleteScavenge(ctx, k, msg)
default:
errMsg := fmt.Sprintf("unrecognized %s message type: %T", types.ModuleName, msg)
return nil, sdkerrors.Wrap(sdkerrors.ErrUnknownRequest, errMsg)
}
}
}
Events
每个处理程序的末尾是一个EventManager
,它将在事务内创建日志,以显示有关在处理此消息期间发生的情况的信息。这对于希望确切了解状态转换结果发生的客户端软件很有用。这些事件使用一系列预定义的类型,可以在./x/scavenge/types/events.go
以下类型中找到它们
package types
// scavenge module event types
const (
// TODO: Create your event types
// EventType<Action> = "action"
EventTypeCreateScavenge = "CreateScavenge"
EventTypeCommitSolution = "CommitSolution"
EventTypeSolveScavenge = "SolveScavenge"
// TODO: Create keys fo your events, the values will be derivided from the msg
// AttributeKeyAddress = "address"
AttributeDescription = "description"
AttributeSolution = "solution"
AttributeSolutionHash = "solutionHash"
AttributeReward = "reward"
AttributeScavenger = "scavenger"
AttributeSolutionScavengerHash = "solutionScavengerHash"
// TODO: Some events may not have values for that reason you want to emit that something happened.
// AttributeValueDoubleSign = "double_sign"
AttributeValueCategory = ModuleName
)
现在,我们有更新的状态所需的所有部件(Message
,Handler
,Keeper
),我们可能会考虑在其中我们可以如何查询状态。通常,这是通过REST端点和/或CLI完成的。这两个客户端都与查询状态的应用程序部分交互,称为Querier
Querier模块代码修改
为了查询应用程序的数据,我们需要使用来使其可访问Querier
。该应用程序的一部分Keeper
与访问状态并返回状态一起工作。在Querier
中定义./x/scavenge/keeper/querier.go
。我们的starport
工具为我们提供了一些外观方面的建议,类似于Handler
我们想要处理不同查询路线的建议。
您可以Querier
针对许多不同类型的查询在内建立许多不同的路由,但我们将只进行三个:
listCommit
将列出所有提交getCommit
将得到一个提交solutionScavengerHash
listScavenge
将列出所有清除getScavenge
将会得到一次清除solutionHash
合并到switch语句中,并充实每个函数,该文件应如下所示
querier.go
package keeper
import (
// this line is used by starport scaffolding # 1
"github.com/github-username/scavenge/x/scavenge/types"
abci "github.com/tendermint/tendermint/abci/types"
sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
)
// NewQuerier creates a new querier for scavenge clients.
func NewQuerier(k Keeper) sdk.Querier {
return func(ctx sdk.Context, path []string, req abci.RequestQuery) ([]byte, error) {
switch path[0] {
// this line is used by starport scaffolding # 2
case types.QueryListCommit:
return listCommit(ctx, k)
case types.QueryGetCommit:
return getCommit(ctx, path[1:], k)
case types.QueryListScavenge:
return listScavenge(ctx, k)
case types.QueryGetScavenge:
return getScavenge(ctx, path[1:], k)
default:
return nil, sdkerrors.Wrap(sdkerrors.ErrUnknownRequest, "unknown scavenge query endpoint")
}
}
}
Types
您可能会注意到,我们在初始switch语句中使用了四种不同的导入类型。这些在我们的./x/scavenge/types/querier.go
文件中定义为简单字符串。该文件应如下所示
package types
const (
QueryListScavenge = "list-scavenge"
QueryGetScavenge = "get-scavenge"
)
const (
QueryListCommit = "list-commit"
QueryGetCommit = "get-commit"
)
我们的查询非常简单,因为我们已经Keeper
为访问状态配备了所有必需的功能。您也可以在这里看到正在使用的迭代器。
现在,我们已经创建了模块的所有基本操作,我们希望使它们可访问。我们可以使用CLI客户端和REST客户端来做到这一点。在本教程中,我们将创建一个CLI客户端
Cli模块代码修改
命令行界面(CLI)将在应用程序在某处机器上运行后帮助我们与它进行交互。每个模块在CLI内都有自己的名称空间,这使它能够创建和签名要由该模块处理的消息。它还具有查询该模块状态的功能。与该应用程序的其余部分结合使用时,CLI将允许您执行诸如为新帐户生成密钥或检查您已经与该应用程序进行交互的状态之类的操作
我们的模块CLI被分成两个文件名为tx.go
以及query.go
分别位于./x/scavenge/client/cli/
。一个文件用于进行包含消息的事务,这些消息最终将更新我们的状态。另一个是进行查询,这将使我们能够从状态中读取信息
tx.go
该tx.go
文件包含GetTxCmd
Cosmos SDK中的标准方法。稍后在module.go
文件中引用该文件,该文件准确描述了模块具有的属性。这使得在实际应用程序级别更容易合并出于不同原因的不同模块。毕竟,我们现在将重点放在模块上,但是稍后我们将创建一个利用该模块以及Cosmos SDK中已经可用的其他模块的应用程序。
在内部,GetTxCmd
我们创建一个新的模块特定命令并调用它scavenge
。在此命令中,我们为定义的每种消息类型添加一个子命令:
GetCmdCreateScavenge
GetCmdCommitSolution
GetCmdRevealSolution
每个函数都从Cobra CLI工具中获取参数以创建一个新的味精,对其进行签名并将其提交给要处理的应用程序。这些函数应该放在tx.go
和tx<Type>.go
文件中,如下所示。
scavenge/x/scavenge/client/cli/tx.go
package cli
import (
"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/github-username/scavenge/x/scavenge/types"
)
// GetTxCmd returns the transaction commands for this module
func GetTxCmd(cdc *codec.Codec) *cobra.Command {
scavengeTxCmd := &cobra.Command{
Use: types.ModuleName,
Short: fmt.Sprintf("%s transactions subcommands", types.ModuleName),
DisableFlagParsing: true,
SuggestionsMinimumDistance: 2,
RunE: client.ValidateCmd,
}
scavengeTxCmd.AddCommand(flags.PostCommands(
// this line is used by starport scaffolding # 1
GetCmdCommitSolution(cdc),
GetCmdRevealSolution(cdc),
GetCmdSetCommit(cdc),
GetCmdDeleteCommit(cdc),
GetCmdCreateScavenge(cdc),
GetCmdSetScavenge(cdc),
GetCmdDeleteScavenge(cdc),
)...)
return scavengeTxCmd
}
scavenge/x/scavenge/client/cli/txScavenge.go
package cli
import (
"bufio"
"crypto/sha256"
"encoding/hex"
"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/github-username/scavenge/x/scavenge/types"
)
func GetCmdCreateScavenge(cdc *codec.Codec) *cobra.Command {
return &cobra.Command{
Use: "create-scavenge [description] [solution] [reward]",
Short: "Creates a new scavenge",
Args: cobra.ExactArgs(3),
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))
var solution = args[1]
var solutionHash = sha256.Sum256([]byte(solution))
var solutionHashString = hex.EncodeToString(solutionHash[:])
reward, err := sdk.ParseCoins(args[2])
if err != nil {
return err
}
msg := types.NewMsgCreateScavenge(cliCtx.GetFromAddress(), args[0], solutionHashString, reward)
err = msg.ValidateBasic()
if err != nil {
return err
}
return utils.GenerateOrBroadcastMsgs(cliCtx, txBldr, []sdk.Msg{msg})
},
}
}
func GetCmdRevealSolution(cdc *codec.Codec) *cobra.Command {
return &cobra.Command{
Use: "reveal-solution [solution]",
Short: "Reveals a solution for scavenge",
Args: cobra.ExactArgs(1), // Does your request require arguments
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))
var solution = args[0]
msg := types.NewMsgRevealSolution(cliCtx.GetFromAddress(), solution)
err := msg.ValidateBasic()
if err != nil {
return err
}
return utils.GenerateOrBroadcastMsgs(cliCtx, txBldr, []sdk.Msg{msg})
},
}
}
func GetCmdSetScavenge(cdc *codec.Codec) *cobra.Command {
return &cobra.Command{
Use: "set-scavenge [description] [solutionHash] [reward] [solution] [scavenger]",
Short: "Set a new scavenge",
Args: cobra.ExactArgs(5),
RunE: func(cmd *cobra.Command, args []string) error {
argsDescription := string(args[0])
argsSolutionHash := string(args[1])
argsReward := string(args[2])
argsSolution := string(args[3])
argsScavenger := string(args[4])
cliCtx := context.NewCLIContext().WithCodec(cdc)
inBuf := bufio.NewReader(cmd.InOrStdin())
txBldr := auth.NewTxBuilderFromCLI(inBuf).WithTxEncoder(utils.GetTxEncoder(cdc))
msg := types.NewMsgSetScavenge(cliCtx.GetFromAddress(), string(argsDescription), string(argsSolutionHash), string(argsReward), string(argsSolution), sdk.AccAddress(argsScavenger))
err := msg.ValidateBasic()
if err != nil {
return err
}
return utils.GenerateOrBroadcastMsgs(cliCtx, txBldr, []sdk.Msg{msg})
},
}
}
func GetCmdDeleteScavenge(cdc *codec.Codec) *cobra.Command {
return &cobra.Command{
Use: "delete-scavenge solutionHash",
Short: "Delete a new scavenge by solutionHash",
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.NewMsgDeleteScavenge(args[0], cliCtx.GetFromAddress())
err := msg.ValidateBasic()
if err != nil {
return err
}
return utils.GenerateOrBroadcastMsgs(cliCtx, txBldr, []sdk.Msg{msg})
},
}
}
scavenge/x/scavenge/client/cli/txCommit.go
package cli
import (
"bufio"
"crypto/sha256"
"encoding/hex"
"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/github-username/scavenge/x/scavenge/types"
)
func GetCmdCommitSolution(cdc *codec.Codec) *cobra.Command {
return &cobra.Command{
Use: "commit-solution [solution]",
Short: "Commits a solution for scavenge",
Args: cobra.ExactArgs(1), // Does your request require arguments
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))
var solution = args[0]
var solutionHash = sha256.Sum256([]byte(solution))
var solutionHashString = hex.EncodeToString(solutionHash[:])
var scavenger = cliCtx.GetFromAddress().String()
var solutionScavengerHash = sha256.Sum256([]byte(solution + scavenger))
var solutionScavengerHashString = hex.EncodeToString(solutionScavengerHash[:])
msg := types.NewMsgCommitSolution(cliCtx.GetFromAddress(), solutionHashString, solutionScavengerHashString)
err := msg.ValidateBasic()
if err != nil {
return err
}
return utils.GenerateOrBroadcastMsgs(cliCtx, txBldr, []sdk.Msg{msg})
},
}
}
func GetCmdSetCommit(cdc *codec.Codec) *cobra.Command {
return &cobra.Command{
Use: "set-commit [id] [solutionHash] [solutionScavengerHash]",
Short: "Set a new commit",
Args: cobra.ExactArgs(3),
RunE: func(cmd *cobra.Command, args []string) error {
id := args[0]
argsSolutionHash := string(args[1])
argsSolutionScavengerHash := string(args[2])
cliCtx := context.NewCLIContext().WithCodec(cdc)
inBuf := bufio.NewReader(cmd.InOrStdin())
txBldr := auth.NewTxBuilderFromCLI(inBuf).WithTxEncoder(utils.GetTxEncoder(cdc))
msg := types.NewMsgSetCommit(cliCtx.GetFromAddress(), id, string(argsSolutionHash), string(argsSolutionScavengerHash))
err := msg.ValidateBasic()
if err != nil {
return err
}
return utils.GenerateOrBroadcastMsgs(cliCtx, txBldr, []sdk.Msg{msg})
},
}
}
func GetCmdDeleteCommit(cdc *codec.Codec) *cobra.Command {
return &cobra.Command{
Use: "delete-commit [id]",
Short: "Delete a new commit 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.NewMsgDeleteCommit(args[0], cliCtx.GetFromAddress())
err := msg.ValidateBasic()
if err != nil {
return err
}
return utils.GenerateOrBroadcastMsgs(cliCtx, txBldr, []sdk.Msg{msg})
},
}
}
query.go
该query.go
文件包含类似的Cobra命令,这些命令保留了一个新的名称空间来引用我们的scavenge
模块。但是,query.go
和query<Type>.go
文件不是创建和提交消息,而是创建查询并以人类可读的形式返回结果。它处理的查询与我们querier.go
先前在文件中定义的查询相同:
GetCmdListCommit
GetCmdGetCommit
GetCmdListScavenge
GetCmdGetScavenge
定义这些命令后,文件应如下所示
query.go
package cli
import (
"fmt"
// "strings"
"github.com/spf13/cobra"
"github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/client/flags"
// "github.com/cosmos/cosmos-sdk/client/context"
"github.com/cosmos/cosmos-sdk/codec"
// sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/github-username/scavenge/x/scavenge/types"
)
// GetQueryCmd returns the cli query commands for this module
func GetQueryCmd(queryRoute string, cdc *codec.Codec) *cobra.Command {
// Group scavenge queries under a subcommand
scavengeQueryCmd := &cobra.Command{
Use: types.ModuleName,
Short: fmt.Sprintf("Querying commands for the %s module", types.ModuleName),
DisableFlagParsing: true,
SuggestionsMinimumDistance: 2,
RunE: client.ValidateCmd,
}
scavengeQueryCmd.AddCommand(
flags.GetCommands(
// this line is used by starport scaffolding # 1
GetCmdListCommit(queryRoute, cdc),
GetCmdGetCommit(queryRoute, cdc),
GetCmdListScavenge(queryRoute, cdc),
GetCmdGetScavenge(queryRoute, cdc),
)...,
)
return scavengeQueryCmd
}
queryScavenge.go
package cli
import (
"fmt"
"github.com/cosmos/cosmos-sdk/client/context"
"github.com/cosmos/cosmos-sdk/codec"
"github.com/github-username/scavenge/x/scavenge/types"
"github.com/spf13/cobra"
)
func GetCmdListScavenge(queryRoute string, cdc *codec.Codec) *cobra.Command {
return &cobra.Command{
Use: "list-scavenge",
Short: "list all scavenge",
RunE: func(cmd *cobra.Command, args []string) error {
cliCtx := context.NewCLIContext().WithCodec(cdc)
res, _, err := cliCtx.QueryWithData(fmt.Sprintf("custom/%s/"+types.QueryListScavenge, queryRoute), nil)
if err != nil {
fmt.Printf("could not list Scavenge\n%s\n", err.Error())
return nil
}
var out []types.Scavenge
cdc.MustUnmarshalJSON(res, &out)
return cliCtx.PrintOutput(out)
},
}
}
func GetCmdGetScavenge(queryRoute string, cdc *codec.Codec) *cobra.Command {
return &cobra.Command{
Use: "get-scavenge [solutionHash]",
Short: "Query a scavenge by solutionHash",
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
cliCtx := context.NewCLIContext().WithCodec(cdc)
solutionHash := args[0]
res, _, err := cliCtx.QueryWithData(fmt.Sprintf("custom/%s/%s/%s", queryRoute, types.QueryGetScavenge, solutionHash), nil)
if err != nil {
fmt.Printf("could not resolve scavenge %s \n%s\n", solutionHash, err.Error())
return nil
}
var out types.Scavenge
cdc.MustUnmarshalJSON(res, &out)
return cliCtx.PrintOutput(out)
},
}
}
queryCommit.go
package cli
import (
"fmt"
"github.com/cosmos/cosmos-sdk/client/context"
"github.com/cosmos/cosmos-sdk/codec"
"github.com/spf13/cobra"
"github.com/github-username/scavenge/x/scavenge/types"
)
func GetCmdListCommit(queryRoute string, cdc *codec.Codec) *cobra.Command {
return &cobra.Command{
Use: "list-commit",
Short: "list all commit",
RunE: func(cmd *cobra.Command, args []string) error {
cliCtx := context.NewCLIContext().WithCodec(cdc)
res, _, err := cliCtx.QueryWithData(fmt.Sprintf("custom/%s/"+types.QueryListCommit, queryRoute), nil)
if err != nil {
fmt.Printf("could not list Commit\n%s\n", err.Error())
return nil
}
var out []types.Commit
cdc.MustUnmarshalJSON(res, &out)
return cliCtx.PrintOutput(out)
},
}
}
func GetCmdGetCommit(queryRoute string, cdc *codec.Codec) *cobra.Command {
return &cobra.Command{
Use: "get-commit [key]",
Short: "Query a commit 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.QueryGetCommit, key), nil)
if err != nil {
fmt.Printf("could not resolve commit %s \n%s\n", key, err.Error())
return nil
}
var out types.Commit
cdc.MustUnmarshalJSON(res, &out)
return cliCtx.PrintOutput(out)
},
}
}
REST
rest/txCommit.go
package rest
import (
"net/http"
"strconv"
"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/github-username/scavenge/x/scavenge/types"
)
// Used to not have an error if strconv is unused
var _ = strconv.Itoa(42)
type createCommitRequest struct {
BaseReq rest.BaseReq `json:"base_req"`
Scavenger string `json:"scavenger"`
SolutionHash string `json:"solutionHash"`
SolutionScavengerHash string `json:"solutionScavengerHash"`
}
func createCommitHandler(cliCtx context.CLIContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req createCommitRequest
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
}
scavenger, err := sdk.AccAddressFromBech32(req.Scavenger)
if err != nil {
rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error())
return
}
parsedSolutionHash := req.SolutionHash
parsedSolutionScavengerHash := req.SolutionScavengerHash
msg := types.NewMsgCommitSolution(
scavenger,
parsedSolutionHash,
parsedSolutionScavengerHash,
)
err = msg.ValidateBasic()
if err != nil {
rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error())
return
}
utils.WriteGenerateStdTxResponse(w, cliCtx, baseReq, []sdk.Msg{msg})
}
}
type setCommitRequest struct {
BaseReq rest.BaseReq `json:"base_req"`
Scavenger string `json:"scavenger"`
SolutionHash string `json:"solutionHash"`
SolutionScavengerHash string `json:"solutionScavengerHash"`
}
func setCommitHandler(cliCtx context.CLIContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req setCommitRequest
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
}
scavenger, err := sdk.AccAddressFromBech32(req.Scavenger)
if err != nil {
rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error())
return
}
parsedSolutionHash := req.SolutionHash
parsedSolutionScavengerHash := req.SolutionScavengerHash
msg := types.NewMsgSetCommit(
scavenger,
req.SolutionHash,
parsedSolutionHash,
parsedSolutionScavengerHash,
)
err = msg.ValidateBasic()
if err != nil {
rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error())
return
}
utils.WriteGenerateStdTxResponse(w, cliCtx, baseReq, []sdk.Msg{msg})
}
}
type deleteCommitRequest struct {
BaseReq rest.BaseReq `json:"base_req"`
Creator string `json:"creator"`
ID string `json:"id"`
}
func deleteCommitHandler(cliCtx context.CLIContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req deleteCommitRequest
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
}
creator, err := sdk.AccAddressFromBech32(req.Creator)
if err != nil {
rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error())
return
}
msg := types.NewMsgDeleteCommit(req.ID, creator)
err = msg.ValidateBasic()
if err != nil {
rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error())
return
}
utils.WriteGenerateStdTxResponse(w, cliCtx, baseReq, []sdk.Msg{msg})
}
}
rest/txScavenge.go
package rest
import (
"net/http"
"strconv"
"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/github-username/scavenge/x/scavenge/types"
)
// Used to not have an error if strconv is unused
var _ = strconv.Itoa(42)
type createScavengeRequest struct {
BaseReq rest.BaseReq `json:"base_req"`
Creator string `json:"creator"`
Description string `json:"description"`
SolutionHash string `json:"solutionHash"`
Reward sdk.Coins `json:"reward"`
Scavenger sdk.AccAddress `json:"creator"`
}
func createScavengeHandler(cliCtx context.CLIContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req createScavengeRequest
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
}
creator, err := sdk.AccAddressFromBech32(req.Creator)
if err != nil {
rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error())
return
}
parsedDescription := req.Description
parsedSolutionHash := req.SolutionHash
parsedReward := req.Reward
msg := types.NewMsgCreateScavenge(
creator,
parsedDescription,
parsedSolutionHash,
parsedReward,
)
err = msg.ValidateBasic()
if err != nil {
rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error())
return
}
utils.WriteGenerateStdTxResponse(w, cliCtx, baseReq, []sdk.Msg{msg})
}
}
type setScavengeRequest struct {
BaseReq rest.BaseReq `json:"base_req"`
ID string `json:"id"`
Creator string `json:"creator"`
Description string `json:"description"`
SolutionHash string `json:"solutionHash"`
Reward string `json:"reward"`
Solution string `json:"solution"`
Scavenger sdk.AccAddress `json:"scavenger"`
}
func setScavengeHandler(cliCtx context.CLIContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req setScavengeRequest
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
}
creator, err := sdk.AccAddressFromBech32(req.Creator)
if err != nil {
rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error())
return
}
parsedDescription := req.Description
parsedSolutionHash := req.SolutionHash
parsedReward := req.Reward
parsedSolution := req.Solution
parsedScavenger := req.Scavenger
msg := types.NewMsgSetScavenge(
creator,
parsedDescription,
parsedSolutionHash,
parsedReward,
parsedSolution,
parsedScavenger,
)
err = msg.ValidateBasic()
if err != nil {
rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error())
return
}
utils.WriteGenerateStdTxResponse(w, cliCtx, baseReq, []sdk.Msg{msg})
}
}
type deleteScavengeRequest struct {
BaseReq rest.BaseReq `json:"base_req"`
Creator string `json:"creator"`
ID string `json:"id"`
}
func deleteScavengeHandler(cliCtx context.CLIContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req deleteScavengeRequest
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
}
creator, err := sdk.AccAddressFromBech32(req.Creator)
if err != nil {
rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error())
return
}
msg := types.NewMsgDeleteScavenge(req.ID, creator)
err = msg.ValidateBasic()
if err != nil {
rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error())
return
}
utils.WriteGenerateStdTxResponse(w, cliCtx, baseReq, []sdk.Msg{msg})
}
}
运行游戏
命令行输入
scavengecli tx scavenge create-scavenge "What's brown and sticky?" "A stick" 69token --from user1
参考 https://tutorials.cosmos.network/scavenge/tutorial/11-play.html
名称服务
构建的应用程序的目标是让用户购买名称并设置这些名称解析为的值。给定名称的所有者将是当前出价最高的人
详细构建应用请参考下面链接
https://tutorials.cosmos.network/nameservice/tutorial/01-app-design.html