Neo智能合约开发者为什么需要异常处理机制

在深入介绍具体的实施方案之前,先通过本文来康康 Neo 智能合约的逻辑轨迹,以便更好地理解新异常处理机制的执行概念。

源代码到操作码
NeoVM 本身无法理解高级编程语言,例如 Python 或 JavaScript。一旦开发人员以他们选择的语言编写了智能合约,就需要将其转换为 NeoVM 能够读取和执行的操作码。
操作码是指:对应于要执行的特定操作,转换为 NeoVM 可以理解的指令。
例如,要在 NeoVM 中执行简单的加法计算,我们可以使用 PUSH1 操作码将数字 1 压入堆栈,然后使用 PUSH2 将数字 2 压入堆栈顶部,最后使用 ADD 操作码,将堆栈中的前两个值加在一起。
将 Python 源代码编译为 Neo2 操作码。
请注意,由于 NeoVM 的设计以及使用 neo-boa 进行编译时的优化,生成的操作码不一定像我们的 PUSH1、PUSH2、ADD 示例那样简单。
转换过程由编译器处理,编译器将智能合约的源代码转换为准备由 NeoVM 执行的操作码。在 Neo 生态系统中,有面向多种语言的编译器,例如核心C#编译器 Neon、Python 的 neo-boa、以及 Go 的 neo-go。

正如我们在上一篇文章中提到,创建新的异常处理机制需要几个新的操作码,并为它们带来在 NeoVM 中捕获和处理异常所需的新逻辑。其中包括机制本身所需的三条指令:TRY、ENDTRY 和 ENDFINALLY,以及根据需要提供的三个引发错误或对 VM 进行故障处理的指令:ABORT、ASSERT、THROW。
操作码加载到 NeoVM
生成智能合约的操作码版本后,下一步是将其加载到 NeoVM 中,通常通过事务将合约部署到 Neo 区块链上来实现。这会将合约存储在每个网络节点上,可以根据需要在其中调用它。
调用由适当命名的 InvocationStack 处理,它是 NeoVM 执行引擎的关键组件。调用智能合约时,合约字节码和任何其他相关参数都将加载到 VM 中,从而创建正在运行的执行上下文。该执行上下文可以被认为是用于执行相关操作的隔离环境。
NeoVM 从初始状态 NONE 开始执行操作,只有当 InvocationStack 为空(进入 HALT 状态)或发生错误(导致 FAULT 状态)才停止。
无论是由于智能合约的 bug 引发无效交易,还是其他原因导致 FAULT。一旦 VM 到达 FAULT,状态是无法恢复的。
因此,在 Neo 智能合约中添加异常处理功能,解决方案就需要确保代码段的执行或失败,不会导致整个调用失败。
执行期间处理异常
为解释这一机制设计,NGD 上海协议组负责人卢川在 Neo 专栏中发表了初始版本的简介。虽然尚未完全解释执行完的实施机制,但卢川引入了该系统的两个重要部分:TryStack 和 ExceptionHandlingContext。
每当 NeoVM 执行 TRY 操作码时,都会创建一个 ExceptionHandlingContext,并将新的上下文添加到 TryStack 中。上下文包含重要信息,例如指向捕获异常时要执行的指令的指针或作为最后一部分可选的 Finally 段的指令的指针。其余两个操作码 ENDTRY 和 ENDFINALLY 用于将 NeoVM 引导至要遵循的下一条指令,以确保代码按预期执行。
每当遇到异常时,都能够在 TryStack 中搜索可以处理该异常的 try-catch。本质上讲,这意味着可以在 catch 部分中检查不同条件时暂停合约的执行,从而使 Neo 上的智能合约可以定义自己的可捕获异常,抢先避免调用错误,或者在不想被察觉到行为的事件中强制制止错误。
对于 Neo 合约开发人员而言,各种操作码的安排都由编译器处理,这意味着他们可以集中精力在源代码级别为其合约添加异常处理。例如,Python 开发人员将能够在他们的代码中使用 try 和 except 代码块,由 neo-boa编译器进行解释,以相同的捕获条件添加到最终的智能合约中。
设计此机制的一个显著好处是它对 try-catch 嵌套的支持,允许在另一个 try-catch 中再执行一个 try-catch。如果第一个 try-catch 没有捕获到异常,则将进入第二个 try-catch,直到被捕获或触发故障为止,从而提供更大的灵活性。
Neo3 智能合约的异常处理机制再一次为 dApp 开发人员提供了一个强大的开发支持,再一次向对开发者最友好的开放平台迈近了一步。