Sui: 深入解析Move VM技术细节

  • King
  • 更新于 6天前
  • 阅读 221

在当今的区块链技术领域,MoveVM作为一种关键的技术组件,在Sui中发挥着重要作用。本文将深入剖析MoveVM的各项技术细节,包括其初始化过程、代码缓存机制、发布模块与脚本的流程,以及函数执行和二进制格式等核心内容。一、MoveVM初始化MoveVM的初始化相对简洁高效,仅需

在当今的区块链技术领域,Move VM作为一种关键的技术组件,在 Sui中 发挥着重要作用。

本文将深入剖析Move VM的各项技术细节,包括其初始化过程、代码缓存机制、发布模块与脚本的流程,以及函数执行和二进制格式等核心内容。

一、Move VM初始化

Move VM的初始化相对简洁高效,仅需初始化一个Loader实例,其内部实质是少量由Mutex保护的空表,如HashMapVec的实例。这一初始化过程成本较低,为后续操作奠定了基础。

Loader在整个VM生命周期中扮演着代码缓存的关键角色,代码并非提前全部加载,而是在运行时,当函数和脚本执行之际按需加载。

一旦加载完成,模块和脚本便以加载后的形式被复用,随时可迅速投入执行,极大提高了执行效率。

二、代码缓存机制

1. 首次加载流程

  • 当VM首次加载一个模块时,它会向数据存储发起查询,获取该模块的二进制数据(以Vec<u8>形式呈现),此过程可能触发网络访问。
  • 紧接着,对获取到的二进制数据进行反序列化与严格验证,确保数据的准确性与完整性。
  • 随后,着手加载模块的依赖项,这意味着对每个依赖项都要重复上述的查询、反序列化、验证及加载步骤。
  • 最后,将模块与其依赖项进行链接,转换为适合运行时的表示形式,并由Loader进行缓存。此后,在该VM实例的生命周期内,对已加载模块的引用无需再次执行上述复杂的获取、验证及转换操作。

2. 缓存一致性

在常见的客户端场景中,系统交易执行硬升级时可能会打破代码缓存的一致性。

此时,适配器需暂停处理交易,直至客户端重启。不同的客户端可能采用各异的“代码模型”,如版本控制机制等。

因而,持有Move VM实例的客户端必须深谙代码缓存的行为特性,提供与已加载代码兼容的数据视图(DataStore)。

并且,当特定条件致使代码缓存一致性改变时,客户端有责任释放当前VM实例并重新实例化新的VM。

三、模块发布流程

客户端若要在系统中发布模块,需调用publish_module函数,该函数接收模块的二进制数据(已序列化)、发送者地址以及GasMeter引用作为参数,VM在接收到发布请求后会执行一系列严谨的步骤:

  1. 首先对传入的模块进行反序列化操作,若反序列化失败,立即返回带有恰当StatusCode的错误信息。
  2. 紧接着核对模块地址与发送者地址是否匹配,这一步骤旨在确保发布者即为最终持有该模块的账户。一旦发现二者不匹配,便返回StatusCode::MODULE_ADDRESS_DOES_NOT_MATCH_SENDER错误。
  3. 由于Move中代码具有不可变性,VM会进一步检查模块是否已被发布,若存在重复发布同名模块的尝试,将触发StatusCode::DUPLICATE_MODULE_NAME错误。
  4. 随后,VM对模块进行加载验证,确保模块在后续引用时能够顺利加载。若验证过程发现模块存在加载问题,同样会返回带有合适StatusCode的错误。
  5. 当上述所有验证均通过后,VM将模块的序列化字节以特定的键写入存储。至此,该模块的任何引用都将被视为有效。

四、脚本执行机制

脚本在Move VM中扮演着执行特定逻辑事务的角色,它本质上是在script块中声明的Move函数,通过调用链上发布的框架来达成任务。VM执行脚本时遵循以下步骤:

  1. 加载脚本与主函数
    • 先计算脚本二进制数据的sha3_256哈希值,此哈希值将作为脚本的唯一标识,用于访问脚本缓存。
    • 依据哈希值查询脚本缓存,判断脚本是否已被加载。若未在缓存中找到,则启动加载流程,若加载失败,执行即刻停止,并返回带有相应StatusCode的错误。
    • 同时,对脚本主函数针对类型参数实例化进行检查,一旦发现错误,执行同样终止并返回错误。
  2. 构建参数列表
    • 脚本执行所需的首类参数是由VM依据senders向量中的账户地址创建的Signer值。
    • 随后,VM会逐一检查args向量中的其他参数,对照预设的许可类型白名单进行筛选,只有匹配白名单的参数才会被添加到脚本的参数列表中。若发现任何不匹配的类型,VM将返回StatusCode::TYPE_MISMATCH错误。
  3. 执行脚本
    • 一切准备就绪后,VM调用解释器执行脚本。在执行过程中,一旦遭遇任何错误,整个交易将被中止,VM同时返回执行失败信息,反之则返回执行成功的标识。

五、脚本函数执行

自Move VM 2.0版本起引入的脚本函数,与常规脚本有诸多相似之处,其字节码源自具有script可见性的链上模块中的Move函数。

执行脚本函数时,VM首先依据模块和函数名从链上模块加载相应函数,并着重检查其是否具备script可见性。

在完成加载与可见性检查后,其余的执行步骤与普通脚本执行流程如出一辙,即构建参数列表、调用解释器执行等。

若在执行过程中发现指定函数不存在,VM将返回FUNCTION_RESOLUTION_FAILURE状态码;若函数不具备script可见性,则会触发EXECUTE_SCRIPT_FUNCTION_CALLED_ON_NON_SCRIPT_VISIBLE状态码。

六、函数执行过程

Move VM提供了强大的功能,允许通过ModuleId和函数名执行模块中的任意函数。鉴于函数名在模块内具有唯一性(不存在重载情况),执行时无需提供函数签名。整个执行过程由VM分三步完成:

  1. 加载函数
    • 首要任务是加载指定的Module,若加载过程出现错误,执行将立即中断,并返回带有合适StatusCode的错误信息。
    • 成功加载模块后,VM在模块内查找目标函数,若无法解析函数,同样返回对应错误。
    • 此外,对于函数的类型参数(若存在),VM会逐个加载,并与函数定义中的类型参数进行严格比对,一旦发现不匹配,即刻返回错误。
  2. 构建参数列表
    • VM依据预设的许可类型白名单对传入的参数进行检查,确保每个参数的类型都符合要求。若存在类型不匹配的情况,VM将返回StatusCode::TYPE_MISMATCH错误。
  3. 执行函数
    • 当函数与参数均准备妥当,VM调用解释器执行函数。在执行期间,若出现任何错误,解释器将被中止,同时返回错误信息。最终,VM返回执行成功或失败的结果。

七、二进制格式剖析

1. 整体架构

  • 模块和脚本在Move VM生态中仅能以二进制形式进出,并且模块在链上也是以二进制格式存储。从逻辑层面看,模块仿若一个汇聚了函数与数据结构的集合,而脚本则更像是一个单纯的入口点,即一个带有参数但无返回值的函数。
  • 二进制数据由头部和一组表构成,为节省空间,大量运用ULEB128算法压缩整数,因为二进制中的大部分数据是以索引形式存在,这种压缩方式成效显著。同时,向量序列化时遵循先存大小(以ULEB128形式)再依次存储元素的规则。

2. 详细组件

  • 二进制头部(Binary Header):作为二进制数据的开篇,包含三个关键部分。Magic固定为4字节的0xA1, 0x1C, 0xEB, 0x0B,可视为一种标识;Version是4字节的小端无符号整数,用于指示版本信息;Table count则以ULEB128形式记录表的数量,当前最大表数量通常限制在1字节内。

  • 表头部(Table Headers):紧跟二进制头部之后,其数量与“Table count”定义相符。每个表头部都有特定格式,包括1字节的Table Kind用于标识表的类型,ULEB128格式的Table Offset指明表内容起始位置相对于表头部末尾的偏移量,以及ULEB128格式的Table Length记录表内容的字节数。各表在存储时必须紧密相连,不得存在间隙,内容也严禁重叠。

  • 各表详情(Tables):涵盖多种类型的表,如MODULE_HANDLES用于标识模块位置,通过一对索引指向ADDRESS_IDENTIFIERS表中的账户地址和IDENTIFIERS表中的模块名;STRUCT_HANDLES能唯一确定用户类型,包含模块、名称、是否为资源以及类型参数等信息;FUNCTION_HANDLES用于精准定位函数,涵盖函数所在模块、名称、参数与返回类型索引以及类型参数等关键要素。其他表如FUNCTION_INSTANTIATIONSSIGNATURESCONSTANT_POOL等也各自承担着描述函数实例化、签名、常量池等重要职责,每个表的格式都经过精心设计,以满足VM运行时的各种需求。

  • 辅助定义(Kinds、SignatureTokens、Bytecodes等)

    • Type Parameter Kind定义了1字节的类型参数种类,如ALL表示可被资源或可复制类型替代,COPYABLE限定只能被可复制类型替代,RESOURCE则要求必须被资源类型替代。
    • SignatureToken作为1字节的标识,用于表示不同的数据类型,像BOOLU8U64等基本类型,以及STRUCTTYPE_PARAMETERVECTOR等复杂类型,通过特定的组合规则能够准确描述函数的参数与返回值类型。
    • Bytecodes构成了Move VM的指令集,每条指令由1字节的操作码(opcode)和可能存在的负载(payload)组成,不同的操作码对应诸如POPRETBR_TRUE等不同的操作,负载则依据操作码的具体需求而定,用于实现VM的各种运算、跳转、数据操作等功能。
  • 模块与脚本特定数据(Module Specific Data、Script Specific Data)

    • 模块的二进制数据末尾有一个ULEB128形式的索引,指向ModuleHandle表,用于明确自身模块在众多模块中的位置。
    • 脚本由于自身特性,不存在FUNCTION_DEFINITIONS表,其入口点相关信息在二进制末尾依次描述,包括若脚本入口点为泛型时的类型参数信息(如数量与种类)、参数类型索引以及函数体字节码(含长度与具体字节码序列)。

结论

综上所述,Move VM通过精心设计的初始化、代码缓存、执行流程以及二进制格式等多方面机制,为区块链应用提供了高效、稳定且可靠的运行环境,助力区块链技术的蓬勃发展。

深入理解这些技术细节,对于开发人员优化基于Move VM的应用、解决潜在问题具有至关重要的意义。

点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
King
King
0x56af...a0dd
擅长Rust/Solidity/FunC/Move开发