您现在的位置是:主页 > 币圈资讯 >

XCM 第三部分:执行和错误管理

2021-09-30 13:07币圈资讯 人已围观

简介 在我写的关于 XCM的前两篇文章中,分别是XCM:跨共识消息格式和XCM 第二部分:版本控制和兼容性我介绍了它的设计...

72cd06dadd1fc64cd3b9b1a90c98f765-sz_98929.gif

在我写的关于 XCM的前两篇文章中,分别是XCM:跨共识消息格式和XCM 第二部分:版本控制和兼容性我介绍了它的设计和版本控制结构的基础知识。在本文中,我们将深入研究其底层设计和执行模型。由于 XCM 是基于 XCVM 的指令集,一个非常高级的虚拟机,这相当于熟悉这种机器架构。

XCVM 是一个非常高级的、非图灵完备的虚拟机。它是基于寄存器的(而不是基于堆栈的)并且有几个专用寄存器,其中大部分保存高度结构化的数据。与通用处理器不同,XCVM 的寄存器不能随意设置为任意值,但有严格的机制来控制它们如何改变。除了与本地链状态交互的某些方式(例如我们已经看到的WithdrawAsset和DepositAsset指令)之外,没有额外的“记忆”。没有循环的可能性,也没有明确的分支指令。

我们已经介绍了两个寄存器:Holding Register,它能够临时持有一个或多个资产,并且可以通过从本地链中提取资产来填充,或者通过从受信任的外部接收资产来填充来源(例如另一个链);和Origin Register,它在执行开始时保存着当前 XCM 执行所源自的共识系统的位置,并且只能变异到内部位置或完全清除。

在其他寄存器中,三个与异常/错误管理有关,两个与跟踪执行权重有关。我们将在本文中了解所有这些。

? 执行模型

如前所述,没有明确的条件指令或循环原语可以多次重新执行相同的指令。这使得预先确定程序的控制流变得相当简单。这个属性很有用,因为我们想要确定XCM 消息在执行点之前可以使用多少执行时间(在整个 Substrate/Polkadot 中称为权重)。

我们期望执行 XCM 的大多数共识平台都需要能够在开始执行之前确定最坏情况的执行时间。这是因为区块链通常需要确保单个块的处理时间不会超过某个预定限制,以免导致整个系统停顿。此外,如果系统需要支付费用,那么它必须在进行支付的工作量之前发生,并且该支付涵盖最坏情况下的执行时间很重要。

由于这种图灵完备性,允许使用图灵完备语言的系统(例如以太坊)实际上无法直接从程序中计算出最坏情况的执行时间。他们通过要求用户预先确定程序的执行资源,然后在它执行时计量它并在它超过支付的数量时中断它来解决这个问题。有时事情会在交易执行之前发生变化并且权重变得不正确。令人高兴的是,像 XCVM 这样不是图灵完备的虚拟机可以避免这种计量和权重规定的需要。

?️‍♀️ 体重

权重通常表示为一个有代表性的硬件执行给定操作所需的整数皮秒数。正如我们在BuyExecution指令中看到的那样,XCVM 在处理某些指令时包含了执行时间/权重的概念。

没有重量计量,但为了允许 XCVM 程序最终采用少于最坏情况重量预测的可能性,我们有一个称为剩余重量寄存器的寄存器。大多数说明不会触及它,因为我们可以准确预测他们将使用多少重量。然而,偶尔会出现最坏情况权重预测高估的情况,只有在执行时我们才知道有多少。虽然通过高估 XCM 消息的权重来计算区块执行时间,但跟踪原始权重被高估的数量并从账户中减去它可以让链优化其区块执行时间配额。

因此,剩余权重寄存器对于我们的块执行时间核算很有用,但并不能单独解决确保支付金额不会被高估的另一个问题。为此,我们需要一个对 的伴随指令BuyExecution,它接收任何多余的重量并将其退还。自然,这条指令存在并被称为RefundSurplus。它使用的第二个寄存器称为“退款重量寄存器”,以确保不会多次退款相同的剩余重量。

? 流量控制和异常

到目前为止,还有两个寄存器在我们对 XCVM 的处理中相当隐含,但仍然很重要。首先是程序寄存器,用于存储当前正在执行的 XCVM 程序。其次是Program Counter,它存储当前正在执行的指令索引。当程序寄存器改变时,它被重置为零,并在每条成功执行的指令结束时增加一。

处理“异常”情况可能性的能力对于编写健壮的代码至关重要。当远程系统上发生了您没有预料到(或确实无法预料到)的事情时,您需要某种方式来管理它,即使它只是将报告发送回源头,说明同样多。

虽然 XCVM 指令集不包括任何明确的通用分支指令,但它的执行模型中确实有一个通用的异常处理框架。XCVM 包括另外两个代码寄存器,每个寄存器都保存一个 XCVM 程序,如程序寄存器。这两个寄存器称为附录寄存器和错误处理程序寄存器。如果您熟悉几种流行语言中的 try/catch/finally 异常系统,那么接下来的内容可能会让人想起。

如前所述,XCVM 程序的执行是按照其中的每条指令一步一步执行的。当它遵循这些指令到程序结束时,会发生以下两种情况之一:要么成功到达程序末尾,要么发生错误。在成功执行的第一种情况下,错误寄存器被清除并将其权重添加到剩余权重寄存器中。附录寄存器也被清除,其内容被放置在程序寄存器中。如果程序寄存器为空,那么我们停止。否则程序计数器复位为零。简而言之,我们扔掉当前程序和错误处理程序,如果有的话就开始执行附录程序。

此功能本身并不是很有用,但与发生错误时发生的情况相结合时会很有用。此处,尚未执行的任何指令的权重都被添加到剩余权重寄存器中。错误处理程序寄存器被清除,其内容放置在程序寄存器中,程序计数器复位为零。简而言之,我们抛出当前程序并开始执行错误处理程序。因为我们没有清除附录寄存器,所以除非它被错误处理程序重置,否则它会在成功完成后执行。

由于其组合结构,它允许错误处理程序的任意“嵌套”:如果需要,错误处理程序也可以有错误处理程序,附录可以有自己的附录。

有两条指令允许操作这些寄存器:SetAppendix和SetErrorHandler. 如您所料,其中一个设置附录寄存器,另一个设置错误处理程序寄存器。其中每一个的预测权重都比其参数的权重略高。然而,当执行时,寄存器中将被替换的 XCM 消息的权重被添加到剩余权重寄存器中,允许回收任何未使用的附录或错误处理程序的权重。

☄️ 投掷错误

有时,实际确保发生错误并自定义该错误的某些方面可能很有用。这已在编写测试代码时使用,但它最终可能会在活动链中找到使用。这可以在 XCVM 中通过Trap总是导致错误发生的指令来完成。抛出的错误类型共享名称Trap。指令和错误都携带一个整数参数,允许在错误抛出者和外部观察者之间传递某种形式的信息。

这是一个简单的例子:

31a5151685e91d88de42c203525fa2f2-sz_17431.png

这Trap会导致DepositAsset跳过final并DepositAsset运行错误处理程序,将 1 DOT(减去执行成本)置于平行链 2000 的所有权下。我们将始终倾向于RefundSurplus在错误处理程序代码的开头使用,因为如果它是运行我们知道很可能使用的预测重量(以及因此购买的重量)是高估的。

? 错误报告

能够引入处理错误的代码非常有用,但经常要求的功能之一是能够将 XCM 消息的结果报告回原始发件人。我们遇到了QueryResponse上一篇文章中的指令,该指令允许一个共识系统向另一个系统报告一些信息,剩下的就是能够以某种方式将 XCM 的结果插入其中QueryResponse并将其发送给希望被告知的人结果。

事实证明,正好有一条指令执行名为ReportError. 它通过使用我们尚未遇到的寄存器来工作:错误寄存器。错误寄存器是一种可选类型(可以设置或清除)。如果已设置,则它包含两条信息:数字索引和 XCM 错误类型。

它具有极其简单的操作机制。首先,每当指令导致错误时,它总是被设置;错误类型设置为该错误的类型,数字索引设置为程序计数器寄存器的值。其次,只有当ClearError指令被执行时它才被清除。该指令是绝对可靠的指令之一——它本身永远不会导致错误。这就是全部——它在发生错误时被设置,并在您发出适当的指令时被清除。

现在应该很清楚ReportError指令是如何工作的:它只是QueryResponse使用错误寄存器的内容组成一条指令并将其发送到特定目的地。当然,在它之前发生的任何错误都会导致指令被跳过,因为执行首先跳转到错误处理程序寄存器的代码,然后跳转到附录寄存器的代码。然而,解决这个问题的方法很简单:放在ReportError附录中将确保它被执行,而不管主代码是否导致执行错误。

我们来看一个简单的例子。我们会将资产(1 DOT)从中继链传送到 Statemint(平行链 1000),在那里购买一些执行时间,然后使用 Statemint 作为储备,我们将资产存入平行链 2000。原始(非错误报告) ) 消息如下所示:

3cbc53b79bc6da59aac17c35f9f0a66a-sz_22742.png

有了基本的错误报告,我们将改为使用这个:

19c8a17cefd849254817dc7d96bc20ed-sz_24148.png

4a5a43e2c98fd70a867028e862ce8e63-sz_11608.png

如您所见,唯一的变化是引入了两条SetAppendix指令,以确保 Statemint 和平行链 2000 中的错误或缺失将报告给中继链。这假设中继链已将自身设置为能够识别和处理QueryResponse源自 Statemint 和平行链 2000 的消息,查询 ID 为 42,权重限制为一千万。令人高兴的是,这确实是 Substrate 支持的很好,但现在不在范围内。

? 资产陷阱

当在处理资产的程序中发生错误时(大多数情况下都是这样,因为它们通常需要使用 来支付执行费用BuyExecution),那么它可能会非常有问题。可能存在BuyExecution指令本身导致错误的情况,可能是因为重量限制不正确或用于支付的资产不足。或者,资产可能被发送到一条无法以有用的方式处理它的链上。在这些情况下,在许多其他情况下,消息的 XCVM 执行以保留寄存器中剩余的资产结束,这些资产与其他寄存器一样是瞬态的,我们希望被遗忘。

团队和他们的用户会很高兴知道 Substrate 的 XCM 允许链完全避免这种损失 ?。该机制分两步工作。首先,持有登记簿中的任何资产在清算后都不会被完全遗忘。如果在 XCVM 停止时保持寄存器不为空,则发出一个包含三个信息的事件:保持寄存器的值;原产地登记簿的原始价值;以及这两条信息的哈希值。Substrate 的 XCM 系统然后将这个散列放在存储中。这部分机制称为资产陷阱。

? 理赔系统

该机制的第二步是能够要求保持寄存器的一些先前内容。这实际上不是通过任何专门为此目的而设计的,而是通过我们尚未遇到的通用指令ClaimAsset. 这是它在 Rust 中的声明方式:

adb942f66ebf4a14518e806bf3638fab-sz_4842.png

此指令的名称可能让人想起我们遇到的某些其他“资助”指令,例如WithdrawAsset和ReceiveTeleportedAsset。如果确实如此,那么这是有充分理由的:确实如此。像其他人一样,它试图将资产(由assets此处的参数给出)放入控股登记册。与WithdrawAsset减少账户链上资产余额的eg 不同,无论原始寄存器的值是多少,都会为这些资产ClaimAsset寻找有效的索赔assets。为了帮助系统找到有效的声明,可以通过ticket参数提供信息。如果找到有效的索赔,则将其从链中删除,并将资产添加到持有登记簿中。

现在,究竟什么构成声明完全取决于链本身。不同的链可能支持不同类型的声明,Substrate 允许您轻松组合它们。但是,正如您可能猜到的那样,当然,一种特殊类型的声明是关于先前删除的保持寄存器内容的声明。

那么让我们来看看这在实践中是如何运作的。假设我们用户的平行链 2000 向 Statemint 发送一条消息,其中它从其主权账户中提取 0.01 DOT 以支付费用,并通知它在国务卿。它可能看起来像这样:

54377cc72bd62e893c62755138803b13-sz_20158.png

假设 0.01 DOT 是足够的费用,并且 Statemint 支持平行链 2000 的本地资产的链上存款(以及使用平行链 2000 作为它的储备),那么这应该可以正常工作。然而,也许 Statemint 尚未成立以识别平行链 2000 的原生资产。在这种情况下,DepositAsset将不知道如何处理资产并因此引发错误。在执行将向平行链 2000 通知此故障的附录之后,我们将剩下 100 个平行链 2000 的本地资产,以及可能在保持寄存器中的一些 DOT。假设费用仅为 0.005 DOT,剩余 0.005 DOT。

然后,Statemint 的 XCM 托盘会记录这些新的可索赔资产的事件,例如:

ce396d215d841858d7e7066ee1fe5ea3-sz_7326.png

一条消息将被发送回平行链 2000,如下所示:

1b34b00cdf22beb918209d73ae4fe912-sz_5612.png

平行链 2000 将在稍后的某个阶段(也许一旦它确定 Statemint 能够接受其本地资产的存款),能够通过一个相当简单的方法收回这 100 个单位:

17fc1b5db31429b2027594540411b020-sz_13805.png

在这种情况下,没有通过票证参数提供特殊信息来帮助定位索赔。这通常适用于资产陷阱索赔,但可能需要将其用于其他类型的索赔。

? 结论

就这样吧——我希望这有助于帮助您更多地了解 XCM 的底层虚拟机,以及它如何帮助您管理意外情况并从意外情况中恢复。本系列的下一篇文章将介绍 XCM 的未来方向以及如何对格式提出改进建议,并深入探讨 Substrate 的 XCM Rust 实现以及我们如何使用它来提供能够轻松解释 XCM 的链。

Polkadot.club积分商城活动来啦!

如果您是波卡的布道者!波卡生态的坚守者!

就请扫码加入我们吧!

进入条件:只要您持仓任意波卡生态项目即可(数量不限)

参与社区日常活动,就可以兑换波卡生态的Token与丰富的礼品!

还等什么呢?一起成为波卡人吧!

Tags:

标签云

站点信息