cosmos-sdk/docs/cn/basics/tx-lifecycle.md

15 KiB
Raw Blame History

Transaction的生命周期

本文档描述了Transaction从创建到提交的生命周期Transaction的定义在其他文档中有详细描述后文中Transaction将统一被称为Tx

创建

Transaction的创建

命令行界面是主要的应用程序界面之一,Tx可以由用户输入以下命令来创建,其中[command]Tx的类型,[args]是相关参数,[flags]是相关配置例如gas price

[appname] tx [command] [args] [flags]

此命令将自动创建Tx,使用帐户的私钥对其进行签名,并将其广播到其他节点。

创建Tx有一些必需的和可选的参数,其中--from指定该Tx的发起账户,例如一个发送代币的Tx,则将从 from指定的账户提取资产。

Gas 和 Fee

此外,用户可以使用这几个参数来表明他们愿意支付多少fee

  • --gas 指的是gas的数量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发送到 recipientAddressTx该命令指定了用户愿意支付的gas其中gas数量为自动估算的1.5倍每单位gas价格为0.025uatom)。

appcli tx send <recipientAddress> 1000uatom --from <senderAddress> --gas auto --gas-adjustment 1.5 --gas-prices 0.025uatom

其他的Transaction创建方法

命令行是与应用程序进行交互的一种简便方法,但是Tx也可以使用REST interface或应用程序开发人员定义的某些其他入口点来创建命令行。从用户的角度来看,交互方式取决于他们正在使用的是页面还是钱包(例如,Tx使用Lunie.io 创建并使用Ledger Nano S对其进行签名

添加到交易池

每个全节点Tendermint节点接收到Tx后都会发送一个名为CheckTxABCI message,用来检查Tx的有效性,CheckTx会返回abci.ResponseCheckTx。 如果Tx通过检查,则将其保留在节点的 交易池(每个节点唯一的内存事务池)中等待出块,Tx如果被发现无效,诚实的节点将丢弃该Tx。在达成共识之前,节点会不断检查传入的Tx并将其广播出去。

检查的类型

全节点在CheckTx期间对Tx先执行无状态检查,然后进行有状态检查,目的是尽早识别并拒绝无效Tx,以免浪费计算资源。

无状态检查不需要知道节点的状态,即轻客户端或脱机节点都可以检查,因此计算开销较小。无状态检查包括确保地址不为空、强制使用非负数、以及定义中指定的其他逻辑。

状态检查根据提交的状态验证TxMessage。例如,检查相关值是否存在并能够进行交易,账户是否有足够的资产,发送方是否被授权或拥有正确的交易所有权。在任何时刻,由于不同的原因,全节点通常具有应用程序内部状态的多种版本。例如,节点将在验证Tx的过程中执行状态更改,但仍需要最后的提交状态才能响应请求,节点不能使用未提交的状态更改来响应请求。

为了验证Tx,全节点调用的CheckTx包括无状态检查和有状态检查,进一步的验证将在DeliverTx阶段的后期进行。其中CheckTx从对Tx进行解码开始。

解码

Tx从应用程序底层的共识引擎如Tendermint被接收时其仍处于[]byte编码形式,需要将其解码才能进行操作。随后,runTx 函数会被调用,并以runTxModeCheck模式运行,这意味着该函数将运行所有检查,但是会在执行Message和写入状态更改之前退出。

ValidateBasic

Message是由module的开发者实现的Msg接口中的一个方法。它应包括基本的无状态完整性检查。例如,如果Message是要将代币从一个账户发送到另一个账户,则ValidateBasic会检查账户是否存在,并确认账户中代币金额为正,但不需要了解状态,例如帐户余额。

AnteHandler

AnteHandler是可选的,但每个应用程序都需要定义。AnteHandler使用副本为特定的Tx执行有限的检查,副本可以使对Tx进行状态检查时无需修改最后的提交状态,如果执行失败,还可以还原为原始状态。

例如, auth模块的AnteHandler检查并增加序列号,检查签名和帐号,并从Tx的第一个签名者中扣除费用,这个过程中所有状态更改都使用checkState

Gas

Context相当于GasMeter,会计算出在Tx的执行过程中多少gas已被使用。用户提供的Tx所需的gas数量称为GasWantedTx在实际执行过程中消耗的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。如果交易池缓存太小而无法跟踪所有TxCheckTx会识别出并拒绝重放的Tx

现有的预防措施包括fee和序列号 计数器,用来区分重放Tx和相同的Tx。如果攻击者尝试向某个节点发送多个相同的Tx,则保留交易池缓存的完整节点将拒绝相同的Tx,而不是在所有Tx上运行CheckTx 。如果Tx有不同的序列号,攻击者会因为需要支付费用而取消攻击。

验证器节点与全节点一样,保留一个交易池以防止重放攻击,但它也用作出块过程中未经验证的交易池。请注意,即使Tx在此阶段通过了所有检查,仍然可能会被发现无效,因为CheckTx没有完全验证TxCheckTx实际上并未执行message)。

写入区块

共识是验证者节点就接受哪些Tx达成协议的过程,它是反复进行的。每个回合都始于出块节点创建一个包含最近Tx的区块,并由验证者节点(具有投票权的特殊全节点)负责达成共识,同意接受该区块或出一个空块。验证者节点执行共识算法,例如Tendermint BFT调用ABCI请求确认Tx,从而达成共识。

达成共识的第一步是区块提案,共识算法从验证者节点中选择一个出块节点来创建和提议一个区块,用来写入TxTx必须在该提议者的交易池中。

状态变更

共识的下一步是执行Tx以完全验证它们所有的全节点收到出块节点广播的区块并调用ABCI函数BeginBlockDeliverTx,和EndBlock。全节点在本地运行的每个过程将产生一个明确的结果,因为message的状态转换是确定性的,并且Tx在提案中有明确的顺序。

         -----------------------------
        |Receive Block Proposal|
         -----------------------------
                         |
                         v
         -----------------------------
        |         BeginBlock         |
         -----------------------------
                         |
                         v
        -----------------------------
        |      DeliverTx(tx0)      |
        |      DeliverTx(tx1)      |
        |      DeliverTx(tx2)      |
        |      DeliverTx(tx3)      |
        |               .                 |
        |               .                 |
        |               .                 |
        -----------------------------
                         |
                         v
        -----------------------------
        |          EndBlock          |
        -----------------------------
                         |
                         v
        -----------------------------
        |          Consensus        |
        -----------------------------
                         |
                         v
        -----------------------------
        |           Commit          |
        -----------------------------

DeliverTx

baseapp中定义的ABCI函数DeliverTx会执行大部分状态转换,DeliverTx会针对共识中确定的顺序,对块中的每个Tx按顺序运行。DeliverTx几乎和CheckTx相同但是会以deliver模式调用runTx函数而不是check模式。全节点不使用checkState,而是使用deliverState

  • **解码:**由于DeliverTx是通过ABCI调用的因此Tx会以[]byte的形式被接收。节点首先会对Tx进行解码,然后在runTxModeDeliver中调用runTxrunTx除了会执行CheckTx中的检查外,还会执行Tx和并写入状态的变化。

  • 检查: 全节点会再次调用validateBasicMsgsAnteHandler。之所以进行第二次检查,是因为在Tx进交易池的过程中,可能没有相同的Tx,但恶意出块节点的区块可能包括无效Tx。但是这次检查特殊的地方在于, AnteHandler不会将gas-prices与节点的min-gas-prices比较,因为每个节点的min-gas-prices可能都不同,这样比较的话可能会产生不确定的结果。

  • 路由和Handler CheckTx退出后,DeliverTx会继续运行 runMsgs 来执行Tx中的每个Msg。由于Tx可能具有来自不同模块的message,因此baseapp需要知道哪个模块可以找到适当的Handler。因此,路由通过模块管理器来检索路由名称并找到对应的Handler

  • Handlerhandler是用来执行Tx中的每个message,并且使状态转换到从而保持deliverTxStatehandlerMsg的模块中定义,并写入模块中的适当存储区。

  • **Gas**在Tx被传递的过程中,GasMeter是用来记录有多少gas被使用如果执行完成GasUsed会被赋值并返回abci.ResponseDeliverTx。如果由于BlockGasMeter 或者 GasMeter 耗尽或其他原因导致执行中断,程序则会报出相应的错误。

如果由于Tx无效或GasMeter用尽而导致任何状态更改失败,Tx的处理将被终止,并且所有状态更改都将还原。区块提案中无效的Tx会导致验证者节点拒绝该区块并投票给空块。

提交

最后一步是让节点提交区块和状态更改,在重跑了区块中所有的Tx之后,验证者节点会验证区块的签名以最终确认它。不是验证者节点的全节点不参与共识(即无法投票),而是接受投票信息以了解是否应提交状态更改。

当收到足够的验证者票数2/3+的加权票数)时,完整的节点将提交一个新的区块,以添加到区块链网络中并最终确定应用程序层中的状态转换。此过程会生成一个新的状态根,用作状态转换的默克尔证明。应用程序使用从Baseapp继承的ABCI方法CommitCommit通过将deliverState写入应用程序的内部状态来同步所有的状态转换。提交状态更改后,checkState从最近提交的状态重新开始,并将deliverState重置为空以保持一致并反映更改。

请注意,并非所有区块都具有相同数量的Tx,并且共识可能会导致一个空块。在公共区块链网络中,验证者可能是拜占庭恶意的,这可能会阻止将Tx提交到区块链中。可能的恶意行为包括出块节点将某个Tx排除在区块链之外,或者投票反对某个出块节点。

至此,Tx的生命周期结束,节点已验证其有效性,并提交了这些更改。Tx本身,以[]byte的形式被存储在区块上进入了区块链网络。

下一节

了解 accounts