docs(cn): match new docs in intro and basics
This commit is contained in:
@@ -0,0 +1,10 @@
|
||||
# 基础文档
|
||||
|
||||
此目录包含对cosmos sdk的基础概念介绍
|
||||
|
||||
1. [SDK应用解析](./app-anatomy.md)
|
||||
2. [交易的生命周期](./tx-lifecycle.md)
|
||||
3. [账户系统](./accounts.md)
|
||||
4. [Gas 和 Fees](./gas-fees.md)
|
||||
|
||||
阅读完基础文档后,可以阅读 [核心文档](../core/README.md) 进一步加深对cosmos的理解。
|
||||
@@ -0,0 +1,130 @@
|
||||
**原文路径:https://github.com/cosmos/cosmos-sdk/blob/master/docs/basics/accounts.md**
|
||||
|
||||
# 账户系统
|
||||
|
||||
# 必备阅读 {hide}
|
||||
|
||||
- [一个SDK程序的剖析](./app-anatomy.md) {prereq}
|
||||
|
||||
## 账户定义
|
||||
|
||||
在Cosmos SDK中,一个账户是指定的一个公私钥对.公钥可以用于派生出`Addresses`,`Addresses`可以在程序里面的各个模块间区分不同的用户.`Addresses`同样可以和[消息](../building-modules/messages-and-queries.md#messages)进行关联用于确定发消息的账户.私钥一般用于生成签名来证明一个消息是被一个`Addresses`(和私钥关联的`Addresses`)所发送.
|
||||
|
||||
Cosmos SDK使用一套称之为 [BIP32](https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki)的标准来生成公私钥.这个标准定义了怎么去创建一个HD钱包(钱包就是一批账户的集合).每一个账户的核心,都有一个种子,每一个种子都有一个12或24个字的助记符.使用这个助记符,使用一种单向的加密方法可以派生出任意数量的私钥.公钥可以通过私钥推导出来.当然,助记符是最敏感的信息,因为可以不停通过助记符来重新生成私钥.
|
||||
|
||||
```
|
||||
Account 0 Account 1 Account 2
|
||||
|
||||
+------------------+ +------------------+ +------------------+
|
||||
| | | | | |
|
||||
| Address 0 | | Address 1 | | Address 2 |
|
||||
| ^ | | ^ | | ^ |
|
||||
| | | | | | | | |
|
||||
| | | | | | | | |
|
||||
| | | | | | | | |
|
||||
| + | | + | | + |
|
||||
| Public key 0 | | Public key 1 | | Public key 2 |
|
||||
| ^ | | ^ | | ^ |
|
||||
| | | | | | | | |
|
||||
| | | | | | | | |
|
||||
| | | | | | | | |
|
||||
| + | | + | | + |
|
||||
| Private key 0 | | Private key 1 | | Private key 2 |
|
||||
| ^ | | ^ | | ^ |
|
||||
+------------------+ +------------------+ +------------------+
|
||||
| | |
|
||||
| | |
|
||||
| | |
|
||||
+--------------------------------------------------------------------+
|
||||
|
|
||||
|
|
||||
+---------+---------+
|
||||
| |
|
||||
| Master PrivKey |
|
||||
| |
|
||||
+-------------------+
|
||||
|
|
||||
|
|
||||
+---------+---------+
|
||||
| |
|
||||
| Mnemonic (Seed) |
|
||||
| |
|
||||
+-------------------+
|
||||
```
|
||||
|
||||
在Cosmos SDK中,账户可以在[`Keybase`](#keybase)中作为一个对象来储存和管理.
|
||||
|
||||
## Keybase
|
||||
|
||||
`Keybase` 是储存和管理账户的对象,在Cosmos SDK中,`Keybase`要实现以下接口
|
||||
|
||||
+++ https://github.com/cosmos/cosmos-sdk/blob/7d7821b9af132b0f6131640195326aa02b6751db/crypto/keys/types.go#L13-L86
|
||||
|
||||
在Cosmos SDK中,`Keybase`接口的默认实现对象是`dbKeybase`.
|
||||
|
||||
+++ https://github.com/cosmos/cosmos-sdk/blob/7d7821b9af132b0f6131640195326aa02b6751db/crypto/keys/keybase.go
|
||||
|
||||
`dbKeybase`上面对`Keybase`接口中方法实现的笔记:
|
||||
|
||||
- `Sign(name, passphrase string, msg []byte) ([]byte, crypto.PubKey, error)`对`message` 字节进行签名.需要做一些准备工作将`message`编码成 [] byte类型,可以参考`auth`模块`message`准备的例子.注意,SDK上面没有实现签名的验证,签名验证被推迟到[`anteHandler`](#antehandler)中进行
|
||||
+++ https://github.com/cosmos/cosmos-sdk/blob/7d7821b9af132b0f6131640195326aa02b6751db/x/auth/types/txbuilder.go#L176-L209
|
||||
- `CreateMnemonic(name string, language Language, passwd string, algo SigningAlgo) (info Info, seed string, err error)`创建一个新的助记符并打印在日志里,但是**并不保存在磁盘上**
|
||||
- `CreateAccount(name, mnemonic, bip39Passwd, encryptPasswd string, account uint32, index uint32) (Info, error)` 基于[`bip44 path`](https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki)创建一个新的账户并将其保存在磁盘上.注意私钥在[保存前用密码加密](https://github.com/cosmos/cosmos-sdk/blob/7d7821b9af132b0f6131640195326aa02b6751db/crypto/keys/mintkey/mintkey.go),**永远不会储存未加密的私钥**.在这个方法的上下文中, `account`和 `address` 参数指的是BIP44派生路径的段(例如`0`, `1`, `2`, ...)用于从助记符派生出私钥和公钥(注意:给相同的助记符和 `account`将派生出相同的私钥,给相同的`account`和`address`也会派生出相同的公钥和`Address`).最后注意`CreateAccount` 方法使用在[Tendermint library](https://github.com/tendermint/tendermint/tree/bc572217c07b90ad9cee851f193aaa8e9557cbc7/crypto/secp256k1)中的`secp256k1` 派生出公私钥和`Address`.总之,这个方法是用来创建用户的钥匙和地址的,并不是共识秘钥,参见[`Addresses`](#addresses) 获取更多信息
|
||||
|
||||
`dbKeybase`的实现是最基本的,并没有根据需求提供锁定功能.锁定功能指如果一个`dbKeybase`实例被创建,底层的`db`就被锁定意味着除了实例化它的程序其他程序无法访问它.这就是SDK程序使用另外一套`Keybase` 接口的实现`lazyKeybase`的原因
|
||||
|
||||
+++ https://github.com/cosmos/cosmos-sdk/blob/7d7821b9af132b0f6131640195326aa02b6751db/crypto/keys/lazy_keybase.go
|
||||
|
||||
`lazyKeybase`是`dbKeybase`的一个简单包装,它仅在要执行操作时锁定数据库,并在之后立即将其解锁。使用`lazyKeybase`[命令行界面](../interfaces/cli.md)可以在 [rest server](../interfaces/rest.md)运行时创建新的账户,它也可以同时传递多个CLI命令
|
||||
|
||||
## 地址和公钥
|
||||
|
||||
`Addresses` 和`PubKey`在程序里面都是标识一个参与者的公共信息.Cosmos SDK默认提供3中类型的`Addresses`和`PubKey`
|
||||
|
||||
- 基于用户的`Addresses` 和`PubKey`,用于指定用户(例如`message`的发送者).它们通过 **`secp256k1`**曲线推导出来
|
||||
- 基于验证节点的`Addresses` 和`PubKey`用于指定验证者的操作员,它们通过 **`secp256k1`**曲线推导出来
|
||||
- 基于共识节点的`Addresses` 和`PubKey`用于指定参与共识的验证着节点,它们通过 **`ed25519`**曲线推导出来
|
||||
|
||||
| | Address bech32 Prefix | Pubkey bech32 Prefix | Curve | Address byte length | Pubkey byte length |
|
||||
|--------------------|-----------------------|----------------------|-------------|---------------------|--------------------|
|
||||
| Accounts | cosmos | cosmospub | `secp256k1` | `20` | `33` |
|
||||
| Validator Operator | cosmosvaloper | cosmosvaloperpub | `secp256k1` | `20` | `33` |
|
||||
| Consensus Nodes | cosmosvalcons | cosmosvalconspub | `ed25519` | `20` | `32` |
|
||||
|
||||
### 公钥
|
||||
|
||||
在Cosmos SDK里面`PubKey`遵循在 tendermint的`crypto`包中定义的`Pubkey`接口
|
||||
|
||||
+++ https://github.com/tendermint/tendermint/blob/bc572217c07b90ad9cee851f193aaa8e9557cbc7/crypto/crypto.go#L22-L27
|
||||
|
||||
对于`secp256k1` 类型的秘钥,具体的实现可以在[这里](https://github.com/tendermint/tendermint/blob/bc572217c07b90ad9cee851f193aaa8e9557cbc7/crypto/secp256k1/secp256k1.go#L140)找到.对于`ed25519`类型的密钥,具体实现可以在[这里](https://github.com/tendermint/tendermint/blob/bc572217c07b90ad9cee851f193aaa8e9557cbc7/crypto/ed25519/ed25519.go#L135)找到.
|
||||
|
||||
请注意,在Cosmos SDK中,`Pubkeys`并非以其原始格式进行操作。它使用[`Amino`](../core/encoding.md#amino)和[`bech32`](https://en.bitcoin.it/wiki/Bech32)进行2次编码.在SDK里面,`Pubkeys`首先调用`Bytes()`方法在原始的 `Pubkey`中(这里面提供amino编码),然后使用`bech32`的`ConvertAndEncode` 方法
|
||||
|
||||
+++ https://github.com/cosmos/cosmos-sdk/blob/7d7821b9af132b0f6131640195326aa02b6751db/types/address.go#L579-L729
|
||||
|
||||
### 地址
|
||||
|
||||
在Cosmos SDK默认提送3种类型的地址
|
||||
|
||||
- `AccAddress` 用于账户
|
||||
- `ValAddress` 用于验证者操作员
|
||||
- `ConsAddress` 用于验证着节点
|
||||
|
||||
这些地址类型都是一种长度为20的十六进制编码的`[]byte`数组的别名,这里有一种标准方法从`Pubkey pub`中获取到地址`aa`.
|
||||
|
||||
```go
|
||||
aa := sdk.AccAddress(pub.Address().Bytes())
|
||||
```
|
||||
|
||||
这些地址实现了 `Address` 接口
|
||||
|
||||
+++ https://github.com/cosmos/cosmos-sdk/blob/7d7821b9af132b0f6131640195326aa02b6751db/types/address.go#L71-L80
|
||||
|
||||
值得注意的是,`Marhsal()`和`Bytes()`方法都返回相同的`[]byte`类型的地址,根据protobuff的兼容性要求我们需要前者.同样,`String()`也被用来返回`bech32`编码类型的地址,这个应该是用户看到的最终编码形式.下面是一个例子:
|
||||
|
||||
+++ https://github.com/cosmos/cosmos-sdk/blob/7d7821b9af132b0f6131640195326aa02b6751db/types/address.go#L229-L243
|
||||
|
||||
## 接下来 {hide}
|
||||
|
||||
学习[gas and fees](./gas-fees.md) {hide}
|
||||
@@ -0,0 +1,241 @@
|
||||
|
||||
|
||||
|
||||
|
||||
# SDK应用程序剖析
|
||||
|
||||
## Node Client
|
||||
|
||||
全节点的核心进程是基于SDK包的。 网络中的参与者运行此过程以初始化其状态机,与其他全节点连接并在新块进入时更新其状态机。
|
||||
|
||||
```
|
||||
^ +-------------------------------+ ^
|
||||
| | | |
|
||||
| | State-machine = Application | |
|
||||
| | | | Built with Cosmos SDK
|
||||
| | ^ + | |
|
||||
| +----------- | ABCI | ----------+ v
|
||||
| | + v | ^
|
||||
| | | |
|
||||
Blockchain Node | | Consensus | |
|
||||
| | | |
|
||||
| +-------------------------------+ | Tendermint Core
|
||||
| | | |
|
||||
| | Networking | |
|
||||
| | | |
|
||||
v +-------------------------------+ v
|
||||
```
|
||||
|
||||
|
||||
|
||||
区块链全节点以二进制形式表示,通常以`-d`后缀表示`守护程序`(例如,`appd`表示` app`或` gaiad`表示`gaia`)。 这个二进制文件是通过编译一个简单的代码文件 main.go构建的,`main.go` 通常位于`./cmd/appd/`中。 此操作通常通过用Makefile编译。
|
||||
|
||||
编译了二进制文件,就可以通过运行[`start`命令](https://docs.cosmos.network/master/core/node.html#start-command) 来启动节点。 此命令功能主要执行三件事:
|
||||
|
||||
1.[`app.go`] 创建了一个状态机实例。
|
||||
|
||||
2.用最新的已知状态初始化状态机,该状态机是从存储在`~/ .appd / data`文件夹中的db中提取的。 此时,状态机的高度为:`appBlockHeight`。
|
||||
|
||||
3.创建并启动一个新的Tendermint实例。 该节点将与对等节点进行连接交换信息。 它将从他们那里获取最新的`blockHeight`,如果它大于本地的`appBlockHeight`,则重播块以同步到该高度。 如果`appBlockHeight`为`0`,则该节点从创世开始,并且Tendermint通过ABCI接口向`app`发送`InitChain`初始化链命令,从而触发[`InitChainer`](https://docs.cosmos.network/master/basics/app-anatomy.html#initchainer)。
|
||||
|
||||
## Core Application File
|
||||
|
||||
通常,状态机的核心是在名为`app.go`的文件中定义的。 它主要包含“应用程序的类型定义”和“创建和初始化它”的功能。
|
||||
|
||||
### Type Definition of the Application
|
||||
|
||||
在app.go中重要的一个是应用程序的type。 它通常由以下部分组成:
|
||||
|
||||
- 在`app.go`中定义的自定义应用程序是`baseapp`的扩展。 当事务由Tendermint发送到应用程序时,`app`使用`baseapp`的方法将它们转送到对应的模块。 baseapp为应用程序实现了大多数核心逻辑,包括所有的[ABCI方法](https://tendermint.com/docs/spec/abci/abci.html#overview)和转送消息逻辑。
|
||||
|
||||
- 一条key链包含整个状态,他是基于 Cosmos SDK 的multistore实现的。 每个模块使用multistore的一个或多个存储来存储其状态。 可以使用在“ app”类型中声明的特定键来访问这些存储。 这些密钥以及`keepers'是Cosmos SDK的 对象功能模型 的核心。
|
||||
- 模块`keeper`的列表。 每个模块 都会抽象定义一个keeper,该keeper 实现模块存储的读写。 一个模块的“ keeper”方法可以从其他模块(如果已授权)中调用,这就是为什么它们在应用程序的类型中声明并作为接口导出到其他模块的原因,以便后者只能访问授权的功能。
|
||||
- 应用程序的`codec`用于序列化和反序列化数据结构以便存储它们,因为存储只能持久化`[]bytes`。 “编解码器”必须是确定性的。 默认编解码器为amino
|
||||
- 模块管理器是一个对象,其中包含应用程序模块的列表。 它简化了与这些模块相关的操作,例如注册routes操作,query route操作或设置各种功能的模块之间顺序执行情况,例如InitChainer操作,BeginBlocke操作和EndBlocker操作
|
||||
- 请参阅[gaia](https://github.com/cosmos/gaia)中的应用程序类型定义示例
|
||||
|
||||
+++ https://github.com/cosmos/gaia/blob/5bc422e6868d04747e50b467e8eeb31ae2fe98a3/app/app.go#L87-L115
|
||||
|
||||
### Constructor Function
|
||||
|
||||
此函数构造了以上部分中定义的类型的新应用程序。 在应用程的start命令中使用,它必须具有AppCreator签名。
|
||||
|
||||
+++ https://github.com/cosmos/cosmos-sdk/blob/7d7821b9af132b0f6131640195326aa02b6751db/server/constructors.go#L20
|
||||
|
||||
以下是此功能执行的主要操作:
|
||||
|
||||
- 创建初始化一个新的codec实例,并使用 基础模块管理器初始化每个应用程序模块的`codec`。
|
||||
- 使用baseapp实例,编解码器和所有适当的存储键的引用实例化一个新应用程序。
|
||||
- 使用每个应用程序模块的`NewKeeper`功能实例化在应用程序的`类型`中定义的所有keeper。 注意,所有keeper必须以正确的顺序实例化,因为一个模块的NewKeeper可能需要引用另一个模块的`keeper`。
|
||||
- 使用每个应用模块的AppModule 来实例化应用程序的模块管理器
|
||||
- 使用模块管理器,初始化应用程序的routes和query route。 当事务由Tendermint通过ABCI中继到应用程序时,它使用此处定义的路由被路由到相应模块的 回调handler。 同样,当应用程序收到查询时,使用此处定义的查询路由将其路由到适当的模块的querier。
|
||||
- 使用模块管理器,注册应用程序的模块的invariants。 invariants是在每个块末尾评估的变量(例如token的总供应量)。 检查不变式的过程是通过InvariantsRegistry 的特殊模块完成的。 invariants应等于模块中定义的预测值。 如果该值与预测的值不同,则将触发不变注册表中定义的特殊逻辑(通常会中断链)。 这对于确保不会发现任何严重错误并产生难以修复的长期影响非常有用。
|
||||
- 使用模块管理器,在每个应用程序的模块 的InitGenesis,BegingBlocker和EndBlocker函数之间设置执行顺序。 请注意,并非所有模块都实现这些功能。
|
||||
- 模块实现这些功能。
|
||||
- 设置其余的应用程序参数:
|
||||
+ `InitChainer`于在应用程序首次启动时对其进行初始化。
|
||||
+ `BeginBlocker`,`EndBlocker`:在每个块的开始和结尾处调用。
|
||||
+ `anteHandler`:用于处理费用和签名验证。
|
||||
- 挂载存储.
|
||||
- 返回应用实例.
|
||||
|
||||
请注意,此函数仅创建该应用的一个实例,而如果重新启动节点,则状态将从`〜/ .appd / data`文件夹中保留下来状态加载,如果节点是第一次启动,则从创世文件生成。See an example of application constructor from [`gaia`](https://github.com/cosmos/gaia):
|
||||
|
||||
+++ https://github.com/cosmos/gaia/blob/f41a660cdd5bea173139965ade55bd25d1ee3429/app/app.go#L110-L222
|
||||
|
||||
### InitChainer
|
||||
|
||||
InitChainer用于根据创始文件(即创始账户的代币余额)初始化应用程序的状态。 当应用程序从Tendermint引擎收到`InitChain`消息时调用该消息,该消息是在节点以`appBlockHeight == 0`(即创世)启动。 应用程序必须通过[`SetInitChainer`](https://godoc.org/github.com/cosmos/cosmos-sdk/baseapp#BaseApp.SetInitChainer )方法设置其[constructor](https://docs.cosmos.network/master/basics/app-anatomy.html#constructor-function)中的`Initchainer`。
|
||||
|
||||
通常,`InitChainer`主要由每个应用程序模块的InitGenesis函数组成。 这是通过调用模块管理器的InitGenesis函数来完成的,而模块管理器的InitGenesis函数将依次调用其包含的每个模块的InitGenesis函数。 请注意,必须使用模块管理器的SetOrderInitGenesis方法设置模块的InitGenesis函数的顺序。 这是在 应用程序的构造函数 application-constructor 中完成的,必须在SetInitChainer之前调用SetOrderInitGenesis。
|
||||
|
||||
查看来自[gaia](https://github.com/cosmos/gaia)的InitChainer的示例:
|
||||
|
||||
See an example of an `InitChainer` from [`gaia`](https://github.com/cosmos/gaia):
|
||||
|
||||
查看来自[`gaia`](https://github.com/cosmos/gaia)的`InitChainer`的示例:
|
||||
+++ https://github.com/cosmos/gaia/blob/f41a660cdd5bea173139965ade55bd25d1ee3429/app/app.go#L235-L239
|
||||
|
||||
### BeginBlocker and EndBlocker
|
||||
|
||||
该SDK为开发人员提供了在其应用程序中实现自定义代码可能性。 这是通过两个名为“ BeginBlocker”和“ EndBlocker”的函数实现的。 当应用程序分别从Tendermint引擎接收到`BeginBlock`和`EndBlock`消息时,将调用它们,它们分别在每个块的开始和结尾处发生。 应用程序必须通过 [SetBeginBlocker](https://godoc.org/github.com/cosmos/cosmos-sdk/baseapp)和[SetEndBlocker](https://godoc.org/github.com/cosmos/cosmos-sdk/baseapp#BaseApp.SetEndBlocker)方法在其 constructor中设置`BeginBlocker`和`EndBlocker`。
|
||||
|
||||
通常,`BeginBlocker`和`EndBlocker`函数主要由每个应用程序模块的`BeginBlock`和`EndBlock`函数组成。 这是通过调用模块管理器的BeginBlock和EndBlock函数来完成的,而后者又会调用其包含的每个模块的BeginBLock和EndBlock函数。 请注意,必须分别在模块管理器中使用SetOrderBeginBlock和SetOrderEndBlock方法来设置模块的BegingBlock和EndBlock函数必须调用的顺序。 这是通过应用程序的构造函数中的模块管理器完成的,必须调用SetOrderBeginBlock和SetOrderEndBlock方法。 在SetBeginBlocker和SetEndBlocker函数之前。
|
||||
|
||||
附带说明,请记住特定于应用程序的区块链是确定性的,这一点很重要。 开发人员必须注意不要在BeginBlocker或EndBlocker中引入不确定性,还必须注意不要使它们在计算上过于昂贵,因为[gas]不会限制计算代价当调用 BeginBlocker和EndBlocker执行。
|
||||
|
||||
请参阅[gaia](https://github.com/cosmos/gaia)中的`BeginBlocker`和`EndBlocker`函数的示例。
|
||||
|
||||
+++ https://github.com/cosmos/gaia/blob/f41a660cdd5bea173139965ade55bd25d1ee3429/app/app.go#L224-L232
|
||||
|
||||
### Register Codec
|
||||
|
||||
MakeCodec函数是app.go文件的最后一个重要功能。 此函数的目的是使用 RegisterCodec 函数实例化 codec`cdc`,例如amino初始化SDK的编解码器以及每个应用程序的模块。
|
||||
|
||||
为了注册应用程序的模块,`MakeCodec`函数在`ModuleBasics`上调用`RegisterCodec`。`ModuleBasics`是一个基本管理器,其中列出了应用程序的所有模块。 它在`init()`函数中得到实例化,仅用于注册应用程序模块的非依赖元素(例如编解码器)。 要了解有关基本模块管理器的更多信息,请点击[这里](https://docs.cosmos.network/master/building-modules/module-manager.html#basicmanager)。
|
||||
|
||||
请参阅[gaia](https://github.com/cosmos/gaia)中的`MakeCodec`示例:
|
||||
|
||||
+++ https://github.com/cosmos/gaia/blob/f41a660cdd5bea173139965ade55bd25d1ee3429/app/app.go#L64-L70
|
||||
|
||||
## Modules
|
||||
|
||||
Modules 是SDK应用程序的灵魂。 它们可以被视为状态机中的状态机。 当交易通过ABCI从底层的Tendermint引擎中继到应用程序时,它由 baseapp 找到对应的模块以便进行处理。 这种范例使开发人员可以轻松构建复杂的状态机,因为他们所需的大多数模块通常已经存在。 对于开发人员而言,构建SDK应用程序所涉及的大部分工作都围绕构建其应用程序尚不存在的自定义模块,并将它们与已经存在的模块集成到一个统一的应用程序中。 在应用程序目录中,标准做法是将模块存储在`x/`文件夹中(不要与SDK的`x/`文件夹混淆,该文件夹包含已构建的模块)。
|
||||
|
||||
### Application Module Interface
|
||||
|
||||
模块必须实现Cosmos SDK AppModuleBasic中的[interfaces](https://docs.cosmos.network/master/building-modules/module-manager.html#application-module-interfaces) 和 AppModule。 前者实现了模块的基本非依赖性元素,例如“编解码器”,而后者则处理了大部分模块方法(包括需要引用其他模块的`keeper`的方法)。 `AppModule`和`AppModuleBasic`类型都在名为`module.go`的文件中定义。
|
||||
|
||||
AppModule在模块上公开了一组有用的方法,这些方法有助于将模块组合成一个一致的应用程序。 这些方法是从模块管理器中调用的,该模块管理应用程序的模块集合。
|
||||
|
||||
### Message Types
|
||||
|
||||
每个`module`定义[messages](https://docs.cosmos.network/master/building-modules/messages-and-queries.html#messages)接口。 每个`transaction`包含一个或多个`messages`。
|
||||
|
||||
当全节点接收到有效的交易块时,Tendermint通过[`DeliverTx`](https://tendermint.com/docs/app-dev/abci-spec.html#delivertx)将每个交易发到应用程序 。 然后,应用程序处理事务:
|
||||
|
||||
- 收到交易后,应用程序首先从`[] bytes`反序列化得到。
|
||||
- 然后,在提取交易中包含的消息之前,它会验证有关交易的一些信息,例如费用支付和签名
|
||||
- 使用message的Type()方法,baseapp可以将其发到对应模块的 回调 handler以便对其进行处理。
|
||||
- 如果消息已成功处理,则状态将更新。
|
||||
|
||||
有关事务生命周期的更多详细信息,请看[这里](./ tx-lifecycle.md)。
|
||||
|
||||
模块开发人员在构建自己的模块时会创建自定义消息类型。 通常的做法是在消息的类型声明前加上`Msg`。 例如,消息类型`MsgSend`允许用户传输tokens:
|
||||
|
||||
+++ https://github.com/cosmos/cosmos-sdk/blob/7d7821b9af132b0f6131640195326aa02b6751db/x/bank/internal/types/msgs.go#L10-L15
|
||||
|
||||
它由`bank`模块的回调`handler`处理,最终会调用`auth`模块来写`keeper`以更新状态。
|
||||
|
||||
### Handler
|
||||
|
||||
回调`handler` 是指模块的一部分,负责处理`baseapp`传递的`message`消息。 仅当通过ABCI接口的DeliverTx 消息从Tendermint 收到事务时,才执行模块的“处理程序”功能。 如果通过CheckTx,仅执行无状态检查和与费用相关的有状态检查。 为了更好地理解`DeliverTx`和`CheckTx`之间的区别以及有状态和无状态检查之间的区别,请看[这里](./ tx-lifecycle.md)。
|
||||
|
||||
模块的“处理程序”通常在名为`handler.go`的文件中定义,并包括:
|
||||
|
||||
- NewHandler 将消息发到对应的回调“ handler”。 该函数返回一个“ handler”函数,此前这个函数在[AppModule`]中注册,以在应用程序的模块管理器中用于初始化应用程序的路由器。 接下来是[nameservice tutorial](https://github.com/cosmos/sdk-tutorials/tree/master/nameservice)的一个例子。
|
||||
+++ https://github.com/cosmos/sdk-tutorials/blob/master/nameservice/x/nameservice/handler.go#L12-L26
|
||||
- 模块定义的每种消息类型的处理函数。 开发人员在这些函数中编写消息处理逻辑。 这通常包括进行状态检查以确保消息有效,并调用[`keeper`](https://docs.cosmos.network/master/basics/app-anatomy.html#keeper)的方法来更新状态。
|
||||
|
||||
处理程序函数返回结果类型为sdk.Result,该结果通知应用程序消息是否已成功处理:
|
||||
|
||||
+++ https://github.com/cosmos/cosmos-sdk/blob/7d7821b9af132b0f6131640195326aa02b6751db/types/result.go#L15-L40
|
||||
|
||||
### Querier
|
||||
|
||||
`Queriers`与`handlers`非常相似,除了它们向状态查询用户而不是处理事务。 最终用户从interface 发起query,最终用户会提供`queryRoute`和一些` data`。 然后使用`queryRoute`通过baseapp`的`handleQueryCustom方法查询到正确的应用程序的`querier` 函数
|
||||
|
||||
+++ https://github.com/cosmos/cosmos-sdk/blob/7d7821b9af132b0f6131640195326aa02b6751db/baseapp/abci.go#L395-L453
|
||||
|
||||
模块的Querier是在名为querier.go的文件中定义的,包括:
|
||||
|
||||
- NewQuerier将查找到对应query函数。 此函数返回一个“`querier`”函数,此前它在 AppModule中注册,以在应用程序的模块管理器中用于初始化应用程序的查询路由器。 请参阅[nameservice demo](https://github.com/cosmos/sdk-tutorials/tree/master/nameservice)中的此类切换示例:
|
||||
+++ https://github.com/cosmos/sdk-tutorials/blob/86a27321cf89cc637581762e953d0c07f8c78ece/nameservice/x/nameservice/internal/keeper/querier.go#L19-L32
|
||||
|
||||
- 对于模块定义的每种需要查询的数据类型,都具有一个查询器功能。 开发人员在这些函数中编写查询处理逻辑。 这通常涉及调用[`keeper`] 的方法来查询状态并将其 序列化为JSON。
|
||||
|
||||
|
||||
### Keeper
|
||||
|
||||
[`Keepers`](https://docs.cosmos.network/master/building-modules/keeper.html)是其模块存储器件。 要在模块的存储区中进行读取或写入,必须使用其`keeper`方法之一。 这是由Cosmos SDK的 object-capabilities 模型确保的。 只有持有商店密钥的对象才能访问它,只有模块的`keeper`才应持有该模块商店的密钥。
|
||||
|
||||
`Keepers`通常在名为`keeper.go`的文件中定义。 它包含`keeper`的类型定义和方法。
|
||||
|
||||
`keeper`类型定义通常包括:
|
||||
|
||||
- 多重存储中模块存储的“密钥”。
|
||||
- 参考**其他模块的`keepers`**。 仅当`keeper`需要访问其他模块的存储(从它们读取或写入)时才需要。
|
||||
- 对应用程序的“编解码器”的引用。 “ keeper”需要它在存储结构之前序列化处理,或在检索它们时将反序列化处理,因为存储仅接受“ [] bytes”作为值。
|
||||
|
||||
与类型定义一起,keeper.go文件的一个重要组成部分是Keeper的构造函数NewKeeper。 该函数实例化上面定义的类型的新`keeper`,并带有`codec`,存储`keys`以及可能引用其他模块的`keeper`作为参数。 从应用程序的构造函数中调用`NewKeeper`函数。 文件的其余部分定义了`keeper`的方法,主要是getter和setter。
|
||||
|
||||
### Command-Line and REST Interfaces
|
||||
|
||||
每个模块都定义了application-interfaces 向用户公开的命令行命令和REST routes。 用户可以创建模块中定义的类型的消息,或查询模块管理的状态的子集。
|
||||
|
||||
#### CLI
|
||||
|
||||
通常,与模块有关的命令在模块文件夹中名为`client / cli`的文件夹中定义。 CLI将命令分为交易和查询两类,分别在`client / cli / tx.go`和`client / cli / query.go`中定义。 这两个命令基于[Cobra Library](https://github.com/spf13/cobra)之上:
|
||||
|
||||
- Transactions命令使用户可以生成新的事务,以便可以将它们包含在块中并更新状态。 应该为模块中定义的每个消息类型message-types创建一个命令。 该命令使用户提供的参数调用消息的构造函数,并将其包装到事务中。 SDK处理签名和其他事务元数据的添加。
|
||||
- 用户可以查询模块定义的状态子集。 查询命令将查询转发到应用程序的查询路由器,然后将查询路由到提供的`queryRoute`参数的相应 querier。
|
||||
|
||||
#### REST
|
||||
|
||||
模块的REST接口允许用户生成事务并通过对应用程序的 light client daemon(LCD) 查询状态。 REST路由在`client / rest / rest.go`文件中定义,该文件包括:
|
||||
|
||||
- `RegisterRoutes`函数,用于注册路由。 从主应用程序的接口 application-interfaces 中为应用程序内使用的每个模块调用此函数。 SDK中使用的路由器是 [Gorilla's mux](https://github.com/gorilla/mux)。
|
||||
- 需要公开的每个查询或事务创建功能的自定义请求类型定义。 这些自定义请求类型基于Cosmos SDK的基本“请求”类型构建:
|
||||
+++ https://github.com/cosmos/cosmos-sdk/blob/7d7821b9af132b0f6131640195326aa02b6751db/types/rest/rest.go#L47-L60
|
||||
|
||||
- 每个请求的一个处理函数可以找到给定的模块。 这些功能实现了服务请求所需的核心逻辑。
|
||||
|
||||
## Application Interface
|
||||
|
||||
Interfaces允许用户与全节点客户端进行交互。 这意味着从全节点查询数据,或者接受全节点中包含在块中的新事务。
|
||||
|
||||
通过汇总在应用程序使用的每个模块中定义的 CLI命令构建SDK应用程序的CLI。 应用程序的CLI通常具有后缀-cli(例如appcli),并在名为`cmd / appcli / main.go`的文件中定义。 该文件包含:
|
||||
|
||||
- main()函数,用于构建appcli接口客户端。这个函数准备每个命令,并在构建它们之前将它们添加到`rootCmd`中。在appCli的根部,该函数添加了通用命令,例如status,keys和config,查询命令,tx命令和rest-server。
|
||||
- 查询命令是通过调用`queryCmd`函数添加的,该函数也在appcli / main.go中定义。此函数返回一个Cobra命令,其中包含在每个应用程序模块中定义的查询命令(从`main()`函数作为`sdk.ModuleClients`数组传递),以及一些其他较低级别的查询命令,例如阻止或验证器查询。查询命令通过使用CLI的命令“ appcli query [query]”来调用。
|
||||
- 通过调用`txCmd`函数来添加**交易命令**。与`queryCmd`类似,该函数返回一个Cobra命令,其中包含在每个应用程序模块中定义的tx命令,以及较低级别的tx命令,例如事务签名或广播。使用CLI的命令`appcli tx [tx]`调用Tx命令。
|
||||
- registerRoutes函数,在初始化 轻客户端(LCD)时从main()函数调用。 “ registerRoutes”调用应用程序每个模块的“ RegisterRoutes”功能,从而注册该模块routes 到LCD的查询路由。可以通过运行以下命令“ appcli rest-server”来启动LCD。
|
||||
|
||||
从[nameservice demo](https://github.com/cosmos/sdk-tutorials/tree/master/nameservice)中查看应用程序的主要命令行文件的示例。
|
||||
|
||||
+++ https://github.com/cosmos/sdk-tutorials/blob/86a27321cf89cc637581762e953d0c07f8c78ece/nameservice/cmd/nscli/main.go
|
||||
|
||||
## Dependencies and Makefile
|
||||
|
||||
因为开发人员可以自由选择其依赖项管理器和项目构建方法。 也就是说,当前最常用的版本控制框架是[`go.mod`](https://github.com/golang/go/wiki/Modules)。 它确保在整个应用程序中使用的每个库都以正确的版本导入。 请参阅[demo](https://github.com/cosmos/sdk-tutorials/tree/master/nameservice)中的示例:
|
||||
|
||||
+++ https://github.com/cosmos/sdk-tutorials/blob/c6754a1e313eb1ed973c5c91dcc606f2fd288811/go.mod#L1-L18
|
||||
|
||||
为了构建应用程序,通常使用[Makefile](https://en.wikipedia.org/wiki/Makefile)。 Makefile主要确保在构建应用程序的两个入口点[`appd`](https://docs.cosmos.network/master/basics/app-anatomy.html#node-client)和[`appcli`](https://docs.cosmos.network/master/basics/app-anatomy.html#application-interface)之前运行`go.mod`。 请参阅 nameservice demo 中的Makefile示例
|
||||
|
||||
+++ https://github.com/cosmos/sdk-tutorials/blob/86a27321cf89cc637581762e953d0c07f8c78ece/nameservice/Makefile
|
||||
|
||||
## Next
|
||||
|
||||
了解有关[交易生命周期](./ tx-lifecycle.md)的更多信息
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
**原文路径:https://github.com/cosmos/cosmos-sdk/blob/master/docs/basics/gas-fees.md**
|
||||
|
||||
# Gas and Fees
|
||||
|
||||
## 必备阅读 {hide}
|
||||
|
||||
- [一个SDK程序的剖析](./app-anatomy.md) {prereq}
|
||||
|
||||
## `Gas` and `Fees`的介绍
|
||||
|
||||
在Cosmos SDK中,`gas`是一种特殊的单位,用于跟踪执行期间的资源消耗.每当对储存进行读写操作的时候会消耗`gas`,如果要进行比较复杂的计算的话也会消耗`gas`.它主要有两个目的:
|
||||
|
||||
- 确保区块不会消耗太多资源而且能顺利完成.这个默认在SDK的 [block gas meter](#block-gas-meter)中保证
|
||||
- 防止来自终端用户的垃圾邮件和滥用.为此,通常会为[`message`](../building-modules/messages-and-queries.md#messages)执行期间的消耗进行定价,并产生`fee` (`fees = gas * gas-prices`).`fees` 通常必须由`message`的发送者来支付.请注意,SDK并没有强制执行对`gas`定价,因为可能会有其他的方法来防止垃圾邮件(例如带宽方案).尽管如此,大多数应用程序仍然会使用`fee` 方式来防止垃圾邮件.这个机制通过[`AnteHandler`](#antehandler)来完成.
|
||||
|
||||
## Gas Meter
|
||||
|
||||
在Cosmos SDK中`gas`是一种简单的`uint64`类型,被称之为*gas meter*的对象进行管理,Gas meters 实现了`GasMeter`接口
|
||||
|
||||
+++ https://github.com/cosmos/cosmos-sdk/blob/7d7821b9af132b0f6131640195326aa02b6751db/store/types/gas.go#L31-L39
|
||||
|
||||
这里:
|
||||
|
||||
- `GasConsumed()`返回`GasMeter`实例中消耗的`gas`的数量
|
||||
- `GasConsumedToLimit()` 返回`GasMeter`实例消耗的gas数量,如果达到上限的话就返回上限
|
||||
- `Limit()`返回`GasMeter`实例的上限,如果是0则表示对`gas`的数量没有限制
|
||||
- `ConsumeGas(amount Gas, descriptor string)` 消耗提供的`gas`,如果`gas`溢出了,使用`descriptor`内容进行报错,如果`gas`并不是无限的,则超过限制就会报错.
|
||||
- `IsPastLimit()` 如果`gas`消耗超过了`GasMeter`的限制则返回`true`,其它返回`false`
|
||||
- `IsOutOfGas()` 如果`gas`消耗大于或等于了`GasMeter`的限制则返回`true`,其它返回`false`
|
||||
|
||||
`GasMeter`通常保存在[`ctx`](../core/context.md)中,`gas`消耗的方式如下:
|
||||
|
||||
```go
|
||||
ctx.GasMeter().ConsumeGas(amount, "description")
|
||||
```
|
||||
|
||||
通常,Cosmos SDK使用两种不同的 `GasMeter`, [main gas meter](#main-gas-metter[)和 [block gas meter](#block-gas-meter).
|
||||
|
||||
### 主 Gas Meter
|
||||
|
||||
`ctx.GasMeter()` 是应用程序的主 `GasMeter`,主 `GasMeter`通过`BeginBlock`中的`setDeliverState`进行初始化,然后跟踪导致状态转换的执行序列中的`gas`消耗.也即是说它的更新由[`BeginBlock`](../core/baseapp.md#beginblock), [`DeliverTx`](../core/baseapp.md#delivertx)和[`EndBlock`](../core/baseapp.md#endblock)进行操作.主 `GasMeter`必须在 [`AnteHandler`](#antehandler)中**设置为0**,以便它能获取每个transaction的Gas消耗
|
||||
|
||||
`gas`消耗可以手工完成,模块开发者通常在 [`BeginBlocker`, `EndBlocker`](../building-modules/beginblock-endblock.md)或者 [`handler`](../building-modules/handler.md)上执行,但大多数情况下,只要对储存区进行了读写,它就会自动完成.这种自动消耗的逻辑在[`GasKv`](../core/store.md#gaskv-store)中完成.
|
||||
|
||||
### 块 Gas Meter
|
||||
|
||||
`ctx.BlockGasMeter()` 是跟踪每个区块`gas`消耗并保证它没有超过限制的`GasMeter`.每当[`BeginBlock`](../core/baseapp.md#beginblock)被调用的时候一个新的 `BlockGasMeter`实例将会被创建. `BlockGasMeter`的`gas`是有限的,每个块的`gas`限制应该在应用程序的共识参数中定义,Cosmos SDK应用程序使用Tendermint提供的默认共识参数:
|
||||
|
||||
+++ https://github.com/tendermint/tendermint/blob/f323c80cb3b78e123ea6238c8e136a30ff749ccc/types/params.go#L65-L72
|
||||
|
||||
当通过`DeliverTx`处理新的[transaction](../core/transactions.md)的时候,`BlockGasMeter`的当前值会被校验是否超过上限,如果超过上限,`DeliverTx` 直接返回,由于`BeginBlock`会消耗`gas`,这种情况可能会在第一个`transaction`到来时发生,如果没有发生这种情况,`transaction`将会被正常的执行.在`DeliverTx`的最后,`ctx.BlockGasMeter()`会追踪`gas`消耗并将它增加到处理`transaction`的`gas`消耗中.
|
||||
|
||||
```go
|
||||
ctx.BlockGasMeter().ConsumeGas(
|
||||
ctx.GasMeter().GasConsumedToLimit(),
|
||||
"block gas meter",
|
||||
)
|
||||
```
|
||||
|
||||
## AnteHandler
|
||||
|
||||
`AnteHandler`是一个特殊的处理程序,它在`CheckTx` 和 `DeliverTx`期间为每一个`transaction`的每个`message`处理之前执行.`AnteHandler`相比`handler`有不同的签名:
|
||||
|
||||
```go
|
||||
// AnteHandler authenticates transactions, before their internal messages are handled.
|
||||
// If newCtx.IsZero(), ctx is used instead.
|
||||
type AnteHandler func(ctx Context, tx Tx, simulate bool) (newCtx Context, result Result, abort bool)
|
||||
```
|
||||
|
||||
`AnteHandler`不是在核心SDK中实现的,而是在每一个模块中实现的,这使开发者可以使用适合其程序需求的`AnteHandler`版本,也就是说当前大多数应用程序都使用 [`auth` module](https://github.com/cosmos/cosmos-sdk/tree/master/x/auth)中定义的默认实现.下面是`AnteHandler`在普通Cosmos SDK程序中的作用:
|
||||
|
||||
- 验证事务的类型正确。 事务类型在实现`anteHandler`的模块中定义,它们遵循事务接口:
|
||||
+++ https://github.com/cosmos/cosmos-sdk/blob/7d7821b9af132b0f6131640195326aa02b6751db/types/tx_msg.go#L33-L41
|
||||
这使开发人员可以使用各种类型的应用程序进行交易。 在默认的auth模块中,标准事务类型为StdTx:
|
||||
+++ https://github.com/cosmos/cosmos-sdk/blob/7d7821b9af132b0f6131640195326aa02b6751db/x/auth/types/stdtx.go#L22-L29
|
||||
- 验证交易中包含的每个[`message`](../building-modules/messages-and-queries.md#messages)的签名,每个`message`应该由一个或多个发送者签名,这些签名必须在`anteHandler`中进行验证.
|
||||
- 在`CheckTx`期间,验证`transaction`提供的 `gas prices`是否大于本地配置`min-gas-prices`(提醒一下,`gas-prices`可以从以下等式中扣除`fees = gas * gas-prices` )`min-gas-prices`是每个独立节点的本地配置,在`CheckTx`期间用于丢弃未提供最低费用的交易.这确保了内存池不会被垃圾交易填充.
|
||||
- 设置 `newCtx.GasMeter` 到0,限制为`GasWanted`.**这一步骤非常重要**,因为它不仅确保交易不会消耗无限的天然气,而且还会在每个`DeliverTx`重置`ctx.GasMeter`(每次 `DeliverTx` 被调用的时候都会执行`anteHandler`,`anteHandler`运行之后`ctx`将会被设置为`newCtx`)
|
||||
|
||||
如上所述,`anteHandler`返回`transaction`执行期间所能消耗的最大的`gas`数量,称之为`GasWanted`.最后实际`gas`消耗数量记为`GasUsed`,因此我们必须使`GasUsed =< GasWanted`.当返回时`GasWanted` 和 `GasUsed`都会被中继到共识引擎中.
|
||||
@@ -0,0 +1,163 @@
|
||||
# Transaction的生命周期
|
||||
|
||||
本文档描述了Transaction从创建到提交的生命周期,Transaction的定义在[其他文档](https://docs.cosmos.network/master/core/transactions.html)中有详细描述,后文中Transaction将统一被称为`Tx`。
|
||||
|
||||
## 创建
|
||||
|
||||
### Transaction的创建
|
||||
|
||||
命令行界面是主要的应用程序界面之一,`Tx`可以由用户输入[以下命令](https://docs.cosmos.network/master/interfaces/cli.html)来创建,其中`[command]`是`Tx`的类型,`[args]`是相关参数,`[flags]`是相关配置例如gas price:
|
||||
|
||||
```bash
|
||||
[appname] tx [command] [args] [flags]
|
||||
```
|
||||
|
||||
此命令将自动**创建**`Tx`,使用帐户的私钥对其进行**签名**,并将其**广播**到其他节点。
|
||||
|
||||
创建`Tx`有一些必需的和可选的参数,其中`--from`指定该`Tx`的发起[账户](https://docs.cosmos.network/master/basics/accounts.html),例如一个发送代币的`Tx`,则将从 `from`指定的账户提取资产。
|
||||
|
||||
#### Gas 和 Fee
|
||||
|
||||
此外,用户可以使用这几个[参数](https://docs.cosmos.network/master/interfaces/cli.html)来表明他们愿意支付多少[fee](https://docs.cosmos.network/master/basics/gas-fees.html):
|
||||
|
||||
* `--gas` 指的是[gas](https://docs.cosmos.network/master/basics/gas-fees.html)的数量,gas代表`Tx`消耗的计算资源,需要消耗多少gas取决于具体的`Tx`,在`Tx`执行之前无法被精确计算出来,但可以通过在`--gas`后带上参数`auto`来进行估算。
|
||||
* `--gas-adjustment`(可选)可用于适当的增加 `gas` ,以避免其被低估。例如,用户可以将`gas-adjustment`设为1.5,那么被指定的gas将是被估算gas的1.5倍。
|
||||
* `--gas-prices` 指定用户愿意为每单位gas支付多少fee,可以是一种或多种代币。例如,`--gas-prices=0.025uatom, 0.025upho`就表明用户愿意为每单位的gas支付0.025uatom 和 0.025upho。
|
||||
* `--fees` 指定用户总共愿意支付的fee。
|
||||
|
||||
所支付fee的最终价值等于gas的数量乘以gas的价格。换句话说,`fees = ceil(gas * gasPrices)`。由于可以使用gas价格来计算fee,也可以使用fee来计算gas价格,因此用户仅指定两者之一即可。
|
||||
|
||||
随后,验证者通过将给定的或计算出的`gas-prices`与他们本地的`min-gas-prices`进行比较,来决定是否在其区块中写入该`Tx`。如果`gas-prices`不够高,该`Tx`将被拒绝,因此鼓励用户支付更多fee。
|
||||
|
||||
#### CLI 示例
|
||||
|
||||
应用程序的用户可以在其CLI中输入以下命令,用来生成一个将1000uatom从`senderAddress`发送到 `recipientAddress`的`Tx`,该命令指定了用户愿意支付的gas(其中gas数量为自动估算的1.5倍,每单位gas价格为0.025uatom)。
|
||||
|
||||
```bash
|
||||
appcli tx send <recipientAddress> 1000uatom --from <senderAddress> --gas auto --gas-adjustment 1.5 --gas-prices 0.025uatom
|
||||
```
|
||||
|
||||
#### 其他的Transaction创建方法
|
||||
|
||||
命令行是与应用程序进行交互的一种简便方法,但是`Tx`也可以使用[REST interface](https://docs.cosmos.network/master/interfaces/rest.html)或应用程序开发人员定义的某些其他入口点来创建命令行。从用户的角度来看,交互方式取决于他们正在使用的是页面还是钱包(例如,`Tx`使用[Lunie.io](https://lunie.io/#/) 创建并使用Ledger Nano S对其进行签名)。
|
||||
|
||||
## 添加到交易池
|
||||
|
||||
每个全节点(Tendermint节点)接收到`Tx`后都会发送一个名为`CheckTx`的[ABCI message](https://tendermint.com/docs/spec/abci/abci.html#messages),用来检查`Tx`的有效性,`CheckTx`会返回`abci.ResponseCheckTx`。
|
||||
如果`Tx`通过检查,则将其保留在节点的 [**交易池**](https://tendermint.com/docs/tendermint-core/mempool.html#mempool)(每个节点唯一的内存事务池)中等待出块,`Tx`如果被发现无效,诚实的节点将丢弃该`Tx`。在达成共识之前,节点会不断检查传入的`Tx`并将其广播出去。
|
||||
|
||||
### 检查的类型
|
||||
|
||||
全节点在`CheckTx`期间对`Tx`先执行无状态检查,然后进行有状态检查,目的是尽早识别并拒绝无效`Tx`,以免浪费计算资源。
|
||||
|
||||
***无状态检查***不需要知道节点的状态,即轻客户端或脱机节点都可以检查,因此计算开销较小。无状态检查包括确保地址不为空、强制使用非负数、以及定义中指定的其他逻辑。
|
||||
|
||||
***状态检查***根据提交的状态验证`Tx`和`Message`。例如,检查相关值是否存在并能够进行交易,账户是否有足够的资产,发送方是否被授权或拥有正确的交易所有权。在任何时刻,由于不同的原因,全节点通常具有应用程序内部状态的[多种版本](https://docs.cosmos.network/master/core/baseapp.html#volatile-states)。例如,节点将在验证`Tx`的过程中执行状态更改,但仍需要最后的提交状态才能响应请求,节点不能使用未提交的状态更改来响应请求。
|
||||
|
||||
为了验证`Tx`,全节点调用的`CheckTx`包括无状态检查和有状态检查,进一步的验证将在[`DeliverTx`](#delivertx)阶段的后期进行。其中`CheckTx`从对`Tx`进行解码开始。
|
||||
|
||||
### 解码
|
||||
|
||||
当`Tx`从应用程序底层的共识引擎(如Tendermint)被接收时,其仍处于`[]byte`[编码](https://docs.cosmos.network/master/core/encoding.html)形式,需要将其解码才能进行操作。随后,[`runTx`](https://docs.cosmos.network/master/core/baseapp.html#runtx-and-runmsgs) 函数会被调用,并以`runTxModeCheck`模式运行,这意味着该函数将运行所有检查,但是会在执行`Message`和写入状态更改之前退出。
|
||||
|
||||
### ValidateBasic
|
||||
|
||||
[Message](https://docs.cosmos.network/master/core/transactions.html#messages)是由module的开发者实现的`Msg`接口中的一个方法。它应包括基本的**无状态**完整性检查。例如,如果`Message`是要将代币从一个账户发送到另一个账户,则`ValidateBasic`会检查账户是否存在,并确认账户中代币金额为正,但不需要了解状态,例如帐户余额。
|
||||
|
||||
### AnteHandler
|
||||
|
||||
[`AnteHandler`](https://docs.cosmos.network/master/basics/gas-fees.html#antehandler)是可选的,但每个应用程序都需要定义。`AnteHandler`使用副本为特定的`Tx`执行有限的检查,副本可以使对`Tx`进行状态检查时无需修改最后的提交状态,如果执行失败,还可以还原为原始状态。
|
||||
|
||||
例如, [`auth`](https://github.com/cosmos/cosmos-sdk/tree/master/x/auth/spec)模块的`AnteHandler`检查并增加序列号,检查签名和帐号,并从`Tx`的第一个签名者中扣除费用,这个过程中所有状态更改都使用`checkState`
|
||||
|
||||
### Gas
|
||||
|
||||
[`Context`](https://docs.cosmos.network/master/core/context.html)相当于`GasMeter`,会计算出在`Tx`的执行过程中多少`gas`已被使用。用户提供的`Tx`所需的`gas`数量称为`GasWanted`。`Tx`在实际执行过程中消耗的`gas`被称为`GasConsumed`,如果`GasConsumed`超过`GasWanted`,将停止执行,并且对状态副本的修改不会被提交。否则,`CheckTx`设置`GasUsed`等于`GasConsumed`并返回结果。在计算完gas和fee后,验证器节点检查用户指定的值`gas-prices`是否小于其本地定义的值`min-gas-prices`。
|
||||
|
||||
### 丢弃或添加到交易池
|
||||
|
||||
如果在`CheckTx`期间有任何失败,`Tx`将被丢弃,并且`Tx`的生命周期结束。如果`CheckTx`成功,则`Tx`将被广播到其他节点,并会被添加到交易池,以便成为待出区块中的候选`Tx`。
|
||||
|
||||
**交易池**保存所有全节点可见的`Tx`,全节点会将其最近的`Tx`保留在**交易池缓存**中,作为防止重放攻击的第一道防线。理想情况下,`mempool.cache_size`的大小足以容纳整个交易池中的所有`Tx`。如果交易池缓存太小而无法跟踪所有`Tx`,`CheckTx`会识别出并拒绝重放的`Tx`。
|
||||
|
||||
现有的预防措施包括fee和`序列号` 计数器,用来区分重放`Tx`和相同的`Tx`。如果攻击者尝试向某个节点发送多个相同的`Tx`,则保留交易池缓存的完整节点将拒绝相同的`Tx`,而不是在所有`Tx`上运行`CheckTx` 。如果`Tx`有不同的`序列号`,攻击者会因为需要支付费用而取消攻击。
|
||||
|
||||
验证器节点与全节点一样,保留一个交易池以防止重放攻击,但它也用作出块过程中未经验证的交易池。请注意,即使`Tx`在此阶段通过了所有检查,仍然可能会被发现无效,因为`CheckTx`没有完全验证`Tx`(`CheckTx`实际上并未执行`message`)。
|
||||
|
||||
## 写入区块
|
||||
|
||||
共识是验证者节点就接受哪些`Tx`达成协议的过程,它是**反复进行**的。每个回合都始于出块节点创建一个包含最近`Tx`的区块,并由验证者节点(具有投票权的特殊全节点)负责达成共识,同意接受该区块或出一个空块。验证者节点执行共识算法,例如[Tendermint BFT](https://tendermint.com/docs/spec/consensus/consensus.html#terms),调用ABCI请求确认`Tx`,从而达成共识。
|
||||
|
||||
达成共识的第一步是**区块提案**,共识算法从验证者节点中选择一个出块节点来创建和提议一个区块,用来写入`Tx`,`Tx`必须在该提议者的交易池中。
|
||||
|
||||
## 状态变更
|
||||
|
||||
共识的下一步是执行`Tx`以完全验证它们,所有的全节点收到出块节点广播的区块并调用ABCI函数[`BeginBlock`](https://docs.cosmos.network/master/basics/app-anatomy.html#beginblocker-and-endblocker),`DeliverTx`,和[`EndBlock`](https://docs.cosmos.network/master/basics/app-anatomy.html#beginblocker-and-endblocker)。全节点在本地运行的每个过程将产生一个明确的结果,因为`message`的状态转换是确定性的,并且`Tx`在提案中有明确的顺序。
|
||||
|
||||
```
|
||||
-----------------------------
|
||||
|Receive Block Proposal|
|
||||
-----------------------------
|
||||
|
|
||||
v
|
||||
-----------------------------
|
||||
| BeginBlock |
|
||||
-----------------------------
|
||||
|
|
||||
v
|
||||
-----------------------------
|
||||
| DeliverTx(tx0) |
|
||||
| DeliverTx(tx1) |
|
||||
| DeliverTx(tx2) |
|
||||
| DeliverTx(tx3) |
|
||||
| . |
|
||||
| . |
|
||||
| . |
|
||||
-----------------------------
|
||||
|
|
||||
v
|
||||
-----------------------------
|
||||
| EndBlock |
|
||||
-----------------------------
|
||||
|
|
||||
v
|
||||
-----------------------------
|
||||
| Consensus |
|
||||
-----------------------------
|
||||
|
|
||||
v
|
||||
-----------------------------
|
||||
| Commit |
|
||||
-----------------------------
|
||||
```
|
||||
|
||||
### DeliverTx
|
||||
|
||||
[`baseapp`](https://docs.cosmos.network/master/core/baseapp.html)中定义的ABCI函数`DeliverTx`会执行大部分状态转换,`DeliverTx`会针对共识中确定的顺序,对块中的每个`Tx`按顺序运行。`DeliverTx`几乎和`CheckTx`相同,但是会以deliver模式调用[`runTx`](../core/baseapp.md#runtx)函数而不是check模式。全节点不使用`checkState`,而是使用`deliverState`。
|
||||
|
||||
|
||||
* **解码:**由于`DeliverTx`是通过ABCI调用的,因此`Tx`会以`[]byte`的形式被接收。节点首先会对`Tx`进行解码,然后在`runTxModeDeliver`中调用`runTx`,`runTx`除了会执行`CheckTx`中的检查外,还会执行`Tx`和并写入状态的变化。
|
||||
|
||||
* **检查:** 全节点会再次调用`validateBasicMsgs`和`AnteHandler`。之所以进行第二次检查,是因为在`Tx`进交易池的过程中,可能没有相同的`Tx`,但恶意出块节点的区块可能包括无效`Tx`。但是这次检查特殊的地方在于, `AnteHandler`不会将`gas-prices`与节点的`min-gas-prices`比较,因为每个节点的`min-gas-prices`可能都不同,这样比较的话可能会产生不确定的结果。
|
||||
|
||||
* **路由和Handler:** `CheckTx`退出后,`DeliverTx`会继续运行 [`runMsgs`](https://docs.cosmos.network/master/core/baseapp.html#runtx-and-runmsgs) 来执行`Tx`中的每个`Msg`。由于`Tx`可能具有来自不同模块的`message`,因此`baseapp`需要知道哪个模块可以找到适当的`Handler`。因此,`路由`通过[模块管理器](https://docs.cosmos.network/master/building-modules/module-manager.html)来检索路由名称并找到对应的[`Handler`](https://docs.cosmos.network/master/building-modules/handler.html)。
|
||||
|
||||
* **Handler:**`handler`是用来执行`Tx`中的每个`message`,并且使状态转换到从而保持`deliverTxState`。`handler`在`Msg`的模块中定义,并写入模块中的适当存储区。
|
||||
|
||||
* **Gas:**在`Tx`被传递的过程中,`GasMeter`是用来记录有多少gas被使用,如果执行完成,`GasUsed`会被赋值并返回`abci.ResponseDeliverTx`。如果由于`BlockGasMeter` 或者 `GasMeter` 耗尽或其他原因导致执行中断,程序则会报出相应的错误。
|
||||
|
||||
如果由于`Tx`无效或`GasMeter`用尽而导致任何状态更改失败,`Tx`的处理将被终止,并且所有状态更改都将还原。区块提案中无效的`Tx`会导致验证者节点拒绝该区块并投票给空块。
|
||||
|
||||
### 提交
|
||||
|
||||
最后一步是让节点提交区块和状态更改,在重跑了区块中所有的`Tx`之后,验证者节点会验证区块的签名以最终确认它。不是验证者节点的全节点不参与共识(即无法投票),而是接受投票信息以了解是否应提交状态更改。
|
||||
|
||||
当收到足够的验证者票数(2/3+的加权票数)时,完整的节点将提交一个新的区块,以添加到区块链网络中并最终确定应用程序层中的状态转换。此过程会生成一个新的状态根,用作状态转换的默克尔证明。应用程序使用从[Baseapp](https://docs.cosmos.network/master/core/baseapp.html)继承的ABCI方法[`Commit`](https://docs.cosmos.network/master/core/baseapp.html#commit),`Commit`通过将`deliverState`写入应用程序的内部状态来同步所有的状态转换。提交状态更改后,`checkState`从最近提交的状态重新开始,并将`deliverState`重置为空以保持一致并反映更改。
|
||||
|
||||
请注意,并非所有区块都具有相同数量的`Tx`,并且共识可能会导致一个空块。在公共区块链网络中,验证者可能是**拜占庭恶意**的,这可能会阻止将`Tx`提交到区块链中。可能的恶意行为包括出块节点将某个`Tx`排除在区块链之外,或者投票反对某个出块节点。
|
||||
|
||||
至此,`Tx`的生命周期结束,节点已验证其有效性,并提交了这些更改。`Tx`本身,以`[]byte`的形式被存储在区块上进入了区块链网络。
|
||||
|
||||
## 下一节
|
||||
|
||||
了解 [accounts](./accounts.md)
|
||||
Reference in New Issue
Block a user