Skip to content

Commit

Permalink
Merge PR Agoric#59: Translate tutorial to chinese base on version of …
Browse files Browse the repository at this point in the history
…2019-04-02
  • Loading branch information
jackzampolin authored Apr 3, 2019
2 parents 26afad5 + f3b42b4 commit 3421bac
Show file tree
Hide file tree
Showing 37 changed files with 2,440 additions and 155 deletions.
6 changes: 4 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@ This repository contains the source code of the nameservice tutorial.

## Tutorial

**[Click here](./tutorial/README.md)** to access the tutorial. You can also view it on the [website](https://cosmos.network/docs/tutorial).
**[Click here](./tutorial/en/README.md)** to access the tutorial. You can also view it on the [website](https://cosmos.network/docs/tutorial).

## Building and running the example

**[Click here](./tutorial/build-run.md)** for instructions on how to build and run the code.
**[Click here](./tutorial/en/build-run.md)** for instructions on how to build and run the code.

Multi-language: [中文](./README_cn.md)
13 changes: 13 additions & 0 deletions README_cn.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# SDK应用教程

本仓库包含名称服务教程的源代码。

## 教程

**[点击这里](./tutorial/cn/README.md)** 跳转到中文教程。你也可以查看 Web 版 [网站](https://cosmos.network/docs/tutorial).

## 构建和运行范例

**[点击这里](./tutorial/cn/build-run.md)** 查看如何构建和运行源代码。

多语言:[English](./README.md)
40 changes: 40 additions & 0 deletions tutorial/cn/01-app-design.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# 程序目标

你正在构建的应用程序的目标是让用户购买域名并为其设置解析的值。给定域名的所有者将是当前最高出价者。在本节中,你将了解如何将这些简单需求转化为程序的设计。

区块链应用程序只是一个[具有确定性的复制状态机](https://en.wikipedia.org/wiki/State_machine_replication)。作为开发人员,你只需定义状态机(即状态,启动状态和触发状态转变的消息),[Tendermint](https://tendermint.com/docs/introduction/introduction.html) 将为你处理通过网络进行复制。

> Tendermint是一个与应用程序无关的引擎,负责处理区块链的网络层和共识层。实际上,这意味着Tendermint负责传播和排序交易字节。Tendermint Core依赖于拜占庭容错(BFT)算法来达成交易顺序的共识。点击[这里](https://tendermint.com/docs/introduction/introduction.html)了解更多Tendermint相关信息。
[Cosmos SDK](https://github.com/cosmos/cosmos-sdk/) 旨在帮助你构建状态机。SDK是一个模块化框架,意味着应用程序是通过将一组可互操作的模块集成在一起构建而成的。每个模块都包含自己的消息/交易处理器,而SDK负责将每条消息路由到其对应模块。

以下是nameservice应用程序所需的模块:

- `auth` : 此模块定义了账户和手续费,并为你应用程序的其余部分提供了访问这些功能的权限。
- `bank` : 此模块使得应用程序能够创建和管理token及余额。
- `nameservice` : 此模块目前还不存在!其将处理你所构建的`nameservice`应用的核心逻辑。它是你构建应用程序时必须使用的主要部分。

> 你可能会好奇为什么没有模块来处理验证人集合的变更。实际上,Tendermint依靠一组验证人来对下一个要添加至区块链的有效交易区块[达成共识](https://tendermint.com/docs/introduction/introduction.html#consensus-overview)。默认情况下,如果没有模块处理验证集合的变更,验证人集合将与创世文件`genesis.json`中定义的验证人集合保持一致。该应用程序就是这种情况。如果要允许更改应用程序的验证人集合,可以使用SDK的 [staking 模块](https://github.com/cosmos/cosmos-sdk/tree/develop/x/staking),或编写自己的模块!
现在,看一下应用程序的两个主要部分:state(状态) 和 message(消息)类型。

## State

state反映了特定时刻你的应用程序。它告诉了每个帐户拥有多少token,每个域名的所有者和价格,以及每个域名的解析值。

token 和帐户的 state 由`auth``bank`模块定义,这意味着你现在不必关心它。你需要做的是定义与你的`nameservice`模块特定相关部分state。

在 SDK 中,所有内容都存储在一个名为`multistore`的存储中。可以在此 multistore 中创建任意数量的键值对存储(在Cosmos SDK中称作[`KVStore`](https://godoc.org/github.com/cosmos/cosmos-sdk/types#KVStore))。在本应用中,我们将使用一个 store 记录 `name``whois` 信息,`name` 的 value、owner 和 price 将存储在一个结构中。

### Message

message 包含在 transaction 中。它们负责触发 state 的转变。每个模块定义了一个 message 列表及如何去处理它们。下面这些 message 是你需要为你的 nameservice 应用去实现的:

- `MsgSetName`: 此 message 允许域名的所有者为指定域名的`nameStore`设置一个值。
- `MsgBuyName`: 此 message 允许账户去购买一个域名并在`ownerStore`中成为所有者。
- 当有人购买一个域名时,他们需要支付币之前所有者购买价格更高的费用。如果域名还没有人购买,那么他们需要燃烧最小价格(`MinPrice`)的代币。

当一条交易(包含在区块中)到达一个Tendermint节点时,它将通过 [ABCI](https://github.com/tendermint/tendermint/tree/master/abci) 传递给应用程序并被解码以得到 message。然后将message路由至对应的模块,并根据定义在`Handler`中的逻辑来进行处理。如果 state 需要更新,`Handler`会调用`Keeper`来执行更新。你将在后面的教程了解有关这些概念的更多信息。

### 现在你已经从高层视角完成了对应用程序的设计,是时候开始[实现](02-app-init.md)它了。

104 changes: 104 additions & 0 deletions tutorial/cn/02-app-init.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
# 开始编写你的程序

首先创建一个新的文件`./app.go`。这个文件是确定性状态机的核心。

`app.go`中,你定义了应用程序在接收交易时执行的操作。但首先,它要能够以正确的顺序接收交易。这是 [Tendermint共识引擎](https://github.com/tendermint/tendermint)的职责。

引入必要的依赖:

```go
package app

import (
"github.com/tendermint/tendermint/libs/log"
"github.com/cosmos/cosmos-sdk/x/auth"

bam "github.com/cosmos/cosmos-sdk/baseapp"
dbm "github.com/tendermint/tendermint/libs/db"
)
```

下面是各引入模块和包的文档:

- [`log`](https://godoc.org/github.com/tendermint/tendermint/libs/log): Tendermint 的日志
- [`auth`](https://godoc.org/github.com/cosmos/cosmos-sdk/x/auth): Cosmos SDK 的`auth`模块
- [`dbm`](https://godoc.org/github.com/tendermint/tendermint/libs/db): Tendermint 的数据库代码
- [`baseapp`](https://godoc.org/github.com/cosmos/cosmos-sdk/baseapp): 如下

这里有几个包是`tendermint`包。Tendermint 通过名为 [ABCI](https://github.com/tendermint/tendermint/tree/master/abci) 的接口将交易从网络传递给应用程序。如果你要查看正在构建的区块链节点的架构,如下所示:

```
+---------------------+
| |
| Application |
| |
+--------+---+--------+
^ |
| | ABCI
| v
+--------+---+--------+
| |
| |
| Tendermint |
| |
| |
+---------------------+
```

幸运的是,你不必实现ABCI接口。Cosmos SDK以[`baseapp`](https://godoc.org/github.com/cosmos/cosmos-sdk/baseapp)的形式提供了它的实现样板。

`baseapp`做了以下几点:

- 解码从 Tendermint 共识引擎接收到的交易。
- 从交易中提取 messages 并做基本的合理性校验。
- 将这些 message 路由到合适的模块使其被正确处理。注意`baseapp`并不了解你想要使用的具体模块。你要做的就是在`app.go`中声明这些模块,在接下来的教程中将会看到这些工作。`baseapp`仅实现了适用于任意模块的核心路由逻辑。
- 如果 ABCI 消息是[`DeliverTx`](https://tendermint.com/docs/spec/abci/abci.html#delivertx)[`CheckTx`](https://tendermint.com/docs/spec/abci/abci.html#checktx))的话就Commit。
- 帮助设置[`BeginBlock`](https://tendermint.com/docs/spec/abci/abci.html#beginblock)[`EndBlock`](https://tendermint.com/docs/spec/abci/abci.html#endblock),这两种消息让你能定义在每个区块开始和结束时执行的逻辑。实际上,每个模块实现了各自的`BeginBlock``EndBlock`子逻辑,app的职责是它们都聚合起来。(注意:你不会在你的应用程序中使用这些消息)
- 帮助初始化你的 state。
- 帮助设置 queries。

现在你需要为应用程序创建一个新的自定义类型`nameServiceApp`。这个类型将嵌入`baseapp`(在Go中的嵌入类似于其他语言中的继承),这意味着它可以访问`baseapp`的所有方法。

```go
const (
appName = "nameservice"
)

type nameServiceApp struct {
*bam.BaseApp
}
```



为你的应用添加一个简单的构造函数:

```go
func NewNameServiceApp(logger log.Logger, db dbm.DB) *nameServiceApp {

// First define the top level codec that will be shared by the different modules. Note: Codec will be explained later
cdc := MakeCodec()

// BaseApp handles interactions with Tendermint through the ABCI protocol
bApp := bam.NewBaseApp(appName, logger, db, auth.DefaultTxDecoder(cdc))

var app = &nameServiceApp{
BaseApp: bApp,
cdc: cdc,
}

return app
}
```



很好!现在你有了应用程序的骨架;但是,仍然缺少具体功能。

`baseapp`不了解你要在应用程序中使用的路由或用户交互。应用程序的主要作用是定义这些路由。另一个作用是定义初始状态。这两件事都要求你向应用程序添加模块。

正如你在应用[程序设计](./01-app-design.md)章节中看到的,你的nameservice需要三个模块:`auth``bank``nameservice`。前两个已经存在了,但最后一个还没有!`nameservice`模块将定义你的状态机的大部分内容。下一步是构建它。

In order to complete your application, you need to include modules. Go ahead and [start building your nameservice module](types.md). You will come back to `app.go` later.

### 为了完成应用程序,你需要引入一些模块。 继续[开始构建你的域名服务模块](./03-types.md) 。 稍后会回到 app.go.
51 changes: 51 additions & 0 deletions tutorial/cn/03-types.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
#类型

我们要做的第一件事是定义一个结构,包含域名所有元数据。 依据 ICANN DNS 术语,我们之后将此结构称为 Whois。

## `types.go`

首先创建文件 `./x/nameservice/types.go` 在其内定义模块自有类型,在 Cosmos SDK 应用中,习惯上将模块相关的代码放在 `./x/` 文件夹中。

## Whois

每个域名有三个预期相关的数据:

- Value - 域名解析出为的值。这是任意字符串,但将来您可以修改它以要求它适合特定格式,例如IP地址,DNS区域文件或区块链地址。
- Owner - 该域名当前所有者的地址。
- Price - 你需要为购买域名支付的费用。

要开始你的 SDK 模块,在 `./x/nameservice/types.go` 文件中定义 `nameservice.Whois` 结构。

```go
package nameservice

import (
"fmt"
"strings"

sdk "github.com/cosmos/cosmos-sdk/types"
)

// Whois is a struct that contains all the metadata of a name
type Whois struct {
Value string `json:"value"`
Owner sdk.AccAddress `json:"owner"`
Price sdk.Coins `json:"price"`
}
```

[设计](./01-app-design.md)文档中提到过,如果名称尚未有所有者,我们希望使用 MinPrice 对其进行初始化。

```go
// Initial Starting Price for a name that was never previously owned
var MinNamePrice = sdk.Coins{sdk.NewInt64Coin("nametoken", 1)}

// Returns a new Whois with the minprice as the price
func NewWhois() Whois {
return Whois{
Price: MinNamePrice,
}
}
```

### 现在我们继续去编写 [Keeper](./04-keeper.md) 模块的代码。
157 changes: 157 additions & 0 deletions tutorial/cn/04-keeper.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
# Keeper

Cosmos SDK模块的主要核心是名为`Keeper`的部分。它处理同存储的交互,引用其他的keeper进行跨模块的交互,并包含模块的大部分核心功能。

首先创建文件`./x/nameservice/keeper.go`来保存模块的keeper。在 Cosmos SDK 应用程序中,模块通常放在`./x/`文件夹中。

## Keeper结构

开始制作你的SDK模块,请在`./x/nameservice/keeper.go`文件中定义`nameservice.Keeper`

```go
package nameservice

import (
"github.com/cosmos/cosmos-sdk/codec"
"github.com/cosmos/cosmos-sdk/x/bank"

sdk "github.com/cosmos/cosmos-sdk/types"
)

// Keeper maintains the link to data storage and exposes getter/setter methods for the various parts of the state machine
type Keeper struct {
coinKeeper bank.Keeper

storeKey sdk.StoreKey // Unexposed key to access store from sdk.Context

cdc *codec.Codec // The wire codec for binary encoding/decoding.
}
```



关于上述代码的几点说明:

- 3个不同的`cosmos-sdk`包被引入:
- [`codec`](https://godoc.org/github.com/cosmos/cosmos-sdk/codec) - 提供负责Cosmos编码格式的工具——[Amino](https://github.com/tendermint/go-amino)
- [`bank`](https://godoc.org/github.com/cosmos/cosmos-sdk/x/bank) - `bank`模块控制账户和转账。
- [`types`](https://godoc.org/github.com/cosmos/cosmos-sdk/types) - `types`包含了整个SDK常用的类型。
- `Keeper`结构体。在 keeper 中有几个关键部分:
- [`bank.Keeper`](https://godoc.org/github.com/cosmos/cosmos-sdk/x/bank#Keeper) : 这是`bank`模块的`Keeper`引用。包括它来允许该模块中的代码调用`bank`模块的函数。SDK使用[`对象能力`](https://en.wikipedia.org/wiki/Object-capability_model)来访问应用程序状态的各个部分。这是为了允许开发人员采用小权限准入原则,限制错误或恶意模块的去影响其不需要访问的状态的能力。
- [`*codec.Codec`](https://godoc.org/github.com/cosmos/cosmos-sdk/codec#Codec) : 这是被Amino用于编码及解码二进制机构的编码解码器的指针。
- [`sdk.StoreKey`](https://godoc.org/github.com/cosmos/cosmos-sdk/types#StoreKey) : 通过它来访问一个持久化保存你的应用程序状态的`sdk.KVStore`
- 模块有1个StoreKey:
- `storeKey` - 这是 name 指向(如 `map[name]Whois`)Whois 结构的主存储空间,

## Getter 和 Setter

现在要添加通过`Keeper`来与存储交互的方法了。首先,添加一个函数来为指定域名设置解析字符串值:

```go
// Sets the entire Whois metadata struct for a name
func (k Keeper) SetWhois(ctx sdk.Context, name string, whois Whois) {
if whois.Owner.Empty() {
return
}
store := ctx.KVStore(k.storeKey)
store.Set([]byte(name), k.cdc.MustMarshalBinaryBare(whois))
}
```

在此方法中,首先使用`Keeper`中的`namesStoreKey`获取`map[name]value`的存储对象。

> 注意:这个函数使用[`sdk.Context`](https://godoc.org/github.com/cosmos/cosmos-sdk/types#Context)。该对象持有访问像`blockHeight``chainID`这样重要部分状态的函数。
接下来,你可以使用方法`.Set([]byte,[]byte)`向存储中插入`<name, value>`键值对。由于存储只接受`[]byte`,想要把`string`转化成`[]byte`再把它们作为参数传给`Set`方法。

接下来,添加一个函数来解析域名(即查找域名对应的解析值):

```go
// Gets the entire Whois metadata struct for a name
func (k Keeper) GetWhois(ctx sdk.Context, name string) Whois {
store := ctx.KVStore(k.storeKey)
if !store.Has([]byte(name)) {
return NewWhois()
}
bz := store.Get([]byte(name))
var whois Whois
k.cdc.MustUnmarshalBinaryBare(bz, &whois)
return whois
}
```

这里,与`SetName`方法一样,首先使用`StoreKey`访问存储。接下来,不使用使用`.Get([] byte) []byte`方法而不是`Set`方法。向函数传参,传递key值,要把`name`字符串转化成`[]byte`,并以`[]byte`的形式返回结果。将此转换成字符串再返回。

如果一个域名尚未在存储中,它返回一个新的 Whois 信息,包含最低价格 MinPrice。

现在,我们添加了根据名称从 store 获取特定参数的功能。 我们重用了 GetWhois 和 SetWhois 函数,而不是重写 store 的 getter 和 setter。 例如,要设置字段,首先我们获取整个 Whois 数据,更新我们的特定字段,然后将新版本放回 store。

```go
// ResolveName - returns the string that the name resolves to
func (k Keeper) ResolveName(ctx sdk.Context, name string) string {
return k.GetWhois(ctx, name).Value
}

// SetName - sets the value string that a name resolves to
func (k Keeper) SetName(ctx sdk.Context, name string, value string) {
whois := k.GetWhois(ctx, name)
whois.Value = value
k.SetWhois(ctx, name, whois)
}

// HasOwner - returns whether or not the name already has an owner
func (k Keeper) HasOwner(ctx sdk.Context, name string) bool {
return !k.GetWhois(ctx, name).Owner.Empty()
}

// GetOwner - get the current owner of a name
func (k Keeper) GetOwner(ctx sdk.Context, name string) sdk.AccAddress {
return k.GetWhois(ctx, name).Owner
}

// SetOwner - sets the current owner of a name
func (k Keeper) SetOwner(ctx sdk.Context, name string, owner sdk.AccAddress) {
whois := k.GetWhois(ctx, name)
whois.Owner = owner
k.SetWhois(ctx, name, whois)
}

// GetPrice - gets the current price of a name. If price doesn't exist yet, set to 1nametoken.
func (k Keeper) GetPrice(ctx sdk.Context, name string) sdk.Coins {
return k.GetWhois(ctx, name).Price
}

// SetPrice - sets the current price of a name
func (k Keeper) SetPrice(ctx sdk.Context, name string, price sdk.Coins) {
whois := k.GetWhois(ctx, name)
whois.Price = price
k.SetWhois(ctx, name, whois)
}
```

SDK 还有一个特性叫 `sdk.Iterator`,可以返回一个迭代器用于遍历指定 store 中的所有 `<Key, Value>` 对。

我们增加一个函数用于获取遍历 store 中所有已知域名的迭代器。

```go
// Get an iterator over all names in which the keys are the names and the values are the whois
func (k Keeper) GetNamesIterator(ctx sdk.Context) sdk.Iterator {
store := ctx.KVStore(k.storeKey)
return sdk.KVStorePrefixIterator(store, []byte{})
}
```

最后需要在`./x/nameservice/keeper.go`文件中加上`Keeper`的构造函数:

```go
// NewKeeper creates new instances of the nameservice Keeper
func NewKeeper(coinKeeper bank.Keeper, storeKey sdk.StoreKey, cdc *codec.Codec) Keeper {
return Keeper{
coinKeeper: coinKeeper,
storeKey: storeKey,
cdc: cdc,
}
}
```

### 接下来,该描述如何让用户通过 [`Msgs` and `Handlers `](./05-msgs-handlers.md) 与刚刚建立的 store 交互。
Loading

0 comments on commit 3421bac

Please sign in to comment.