ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

Aptos 中的 Move 语言扩展机制:从 Table 扩展看原生能力集成与实现原理

Aptos 中的 Move 语言扩展机制:从 Table 扩展看原生能力集成与实现原理 Aptos 中的 Move 语言扩展机制从 Table 扩展看原生能力集成与实现原理【免费下载链接】aptos-coreAptos is a layer 1 blockchain built to support the widespread use of blockchain through better technology and user experience.项目地址: https://gitcode.com/GitHub_Trending/ap/aptos-core本文聚焦 Aptos 仓库中third_party/move/extensions目录所承载的Move 语言与运行时扩展机制。Move 是一门面向资源的智能合约语言其核心语言与 VM 运行时保持精简而扩展extension则为在保持核心稳定性的前提下向语言注入新能力提供了官方通道。通过本文你将掌握扩展为何默认不启用、如何在 Move CLI / 包系统中启用扩展特性以及如何在自定义适配器adapter中通过NativeContextExtensions与原生函数表将扩展接入 VM 会话并深入理解以 Table 扩展为代表的扩展在语言层、原生层与测试层的完整实现。扩展机制概览核心语言之外的可插拔能力third_party/move/extensions/README.md对该目录给出了明确定位本目录包含对 Move 核心语言与运行时的扩展。这些扩展默认不启用但可以按照每个扩展各自的说明集成到基于 Move 的环境中。部分扩展在经过一段时间的稳定期后可能最终成为核心语言的一部分。这段话包含了三个关键信息扩展与核心分离扩展不属于 Move 语言规范的一部分也不随默认编译开启因此核心语言的演进不会被扩展实验性改动拖累集成路径明确每个扩展自带独立的 README 与集成说明适配器作者需要显式地把扩展的原生函数与上下文注册进 VM稳定后可能转正这是扩展机制的生命周期设计——实验性能力先在扩展层验证成熟后有望并入核心语言。从当前仓库看该目录下实际承载的扩展实例是move-table-extension即面向 Move 的大规模存储表large-scale storage tables扩展其完整工程布局为third_party/move/extensions/ └── move-table-extension/ ├── Cargo.toml # Rust crate 定义 ├── Move.toml # Move 包清单 ├── README.md # 扩展的集成指南CLI 与适配器两种路径 ├── sources/ │ ├── Table.move # 语言层Table API 与 native 函数声明 │ └── Table.spec.move # 形式化验证规格Move Prover ├── src/ │ └── lib.rs # 原生层TableHandle、TableResolver、8 个 native 函数 └── tests/ ├── move_unit_tests.rs # Rust 侧挂载扩展并驱动 Move 单测 └── table_tests.move # Move 侧功能测试用例下文将以通用扩展集成流程为骨架、以 Table 扩展为贯穿始终的实例展开。路径一在 Move CLI 与包系统中启用扩展move-table-extension/README.md明确指出要在 Move CLI 和包系统中使用该扩展编译时必须开启 featurefeature [table-extension]也就是说Table 扩展在编译器与测试框架层面是通过 Cargo feature 门控的。这一点在扩展自身的 Cargo.toml 中也有直接印证其 dev-dependencies 声明了move-unit-test { workspace true, features [table-extension] }即运行本扩展的 Move 单元测试时单元测试框架本身必须携带table-extensionfeature否则测试环境不认识 Table 相关的原生函数。包系统一侧扩展自带独立的 Move 包清单 Move.toml[package] name MoveTableExtension version 1.0.0 [addresses] extensions _ [dev-addresses] std 0x1 extensions 0x2 [dependencies] MoveStdlib { local ../../move-stdlib } MoveNursery { local ../../move-stdlib/nursery }其中extensions _表示扩展模块地址在编译期才被确定_为未绑定占位dev 地址把extensions固定在0x2、标准库固定在0x1这与原生函数注册时使用的地址参数保持一致见下文table_natives(extension_addr)依赖本地move-stdlib与move-stdlib/nursery说明扩展只依赖标准库无需引入其他第三方包。路径二在自定义适配器中集成扩展Move 的 VMmove_vm_runtime允许宿主环境adapter在创建会话时注入原生上下文扩展NativeContextExtensions与原生函数表NativeFunctionTable。move-table-extension/README.md给出了完整的集成代码骨架这是把 Table 能力接入任意 Move 环境的官方标准姿势use move_core_types::account_address::AccountAddress; use move_stdlib::natives; use move_table_extension::NativeTableContext; use move_vm_runtime::move_vm::MoveVM; use move_vm_runtime::native_functions::NativeContextExtensions; fn run() { let resource_resolver unimplemented!(); // a resource resolver the adapter provides let txn_hash unimplemented!(); // a unique hash for table creation for this transaction let table_resolver unimplemented!(); // a remote table resolver the adapter provides let std_addr unimplemented!(); // address where to deploy the std lib let extension_addr unimplemented!(); // address where to deploy the table extension let mut extensions NativeContextExtensions::default(); extensions.add(NativeTableContext::new(txn_hash, table_resolver)); let mut natives move_stdlib::natives::all_natives(std_addr); natives.append(mut move_table_extension::table_natives(extension_addr)); let vm MoveVM::new(natives); let session vm.new_session_with_extensions(resource_resolver, extensions); let result session.execute_function(..)?; let (change_set, events, extensions) session.finish_with_extensions()?; let table_change_set extensions.get::NativeTableContext().into_change_set(); // Do something with the table change set // ... }这个骨架可以拆成四个关键步骤来理解构造上下文扩展NativeTableContext::new(txn_hash, table_resolver)需要两样东西——txn_hash当前交易的唯一哈希Table 用它派生表句柄handle保证不同交易创建的表全局唯一且确定table_resolver一个由适配器提供的远程表解析器实现TableResolvertrait用于从持久化存储中按句柄 键读取条目。合并原生函数表标准库原生函数all_natives(std_addr)与扩展原生函数table_natives(extension_addr)拼接成完整的NativeFunctionTable再交给MoveVM::new。std_addr与extension_addr分别对应标准库与扩展模块的部署地址在 Move.toml 中即0x1与0x2。以扩展开启会话new_session_with_extensions把上下文扩展带入会话使执行期间 Table 原生函数能随时访问NativeTableContext。回收表变更集会话结束调用finish_with_extensions()后通过extensions.get::NativeTableContext().into_change_set()取出TableChangeSet其中记录了新建表、删除表以及每个表内的条目增删改Op::New / Modify / Delete适配器需要把这些变更持久化到自己的存储中。适配器必须提供的 TableResolver扩展无法脱离宿主的存储而存在。lib.rs 中定义了扩展对宿主的最小存储抽象pub trait TableResolver { fn resolve_table_entry_bytes_with_layout( self, handle: TableHandle, key: [u8], maybe_layout: OptionMoveTypeLayout, ) - ResultOptionBytes, PartialVMError; }适配器实现该 trait 后Table 原生函数即可按需从远程存储懒加载条目maybe_layout允许宿主在已知值布局时跳过反序列化开销。语言层实现Table 模块的公开 API 与设计扩展的语言面位于 sources/Table.move模块名为extensions::table。其核心类型定义如下struct Tablephantom K: copy drop, phantom V has store { handle: address, length: u64, }注意Table只有store能力且 K、V 均为 phantom 类型参数——真正决定键值布局的是句柄背后由原生层管理的BoxV资源。BoxV has key, drop, store { val: V }是一个内部包装类型值以资源形式存放在原生层的盒中这正是让表条目可以像资源一样被管理、序列化与持久化的关键设计。公开 API 一览函数签名语义中止条件newK, V()创建空表内部调用new_table_handle生成句柄无add(mut t, key, val)写入新条目键已存在EALREADY_EXISTS 100borrow(t, key): V不可变借用键不存在ENOT_FOUND 101borrow_mut(mut t, key): mut V可变借用键不存在ENOT_FOUND 101borrow_mut_with_default(mut t, key, default)键不存在时先写入默认值再借用无remove(mut t, key): V移除并返回值length减一键不存在ENOT_FOUND 101contains(t, key): bool判断键是否存在无length(t): u64返回条目数无empty(t): bool是否为空表无destroy_empty(t)销毁空表表非空ENOT_EMPTY 102drop_unchecked(t)#[test_only]测试专用非空也可丢弃无其中add与remove会同步维护table.length计数器而borrow/borrow_mut/contains只读操作不触碰计数器。destroy_empty在 Move 侧先断言length 0errors::invalid_state(ENOT_EMPTY)再调用原生destroy_empty_box。8 个原生函数的声明Table.move底部声明了全部 8 个 native 函数它们额外带一个类型参数B即BoxV原生层可利用该类型推导值的序列化布局native fun new_table_handleK, V(): address; native fun add_boxK: copy drop, V, B(table: mut TableK, V, key: K, val: BoxV); native fun borrow_boxK: copy drop, V, B(table: TableK, V, key: K): BoxV; native fun borrow_box_mutK: copy drop, V, B(table: mut TableK, V, key: K): mut BoxV; native fun contains_boxK: copy drop, V, B(table: TableK, V, key: K): bool; native fun remove_boxK: copy drop, V, B(table: mut TableK, V, key: K): BoxV; native fun destroy_empty_boxK: copy drop, V, B(table: TableK, V); native fun drop_unchecked_boxK: copy drop, V, B(table: TableK, V);原生层实现句柄生成、上下文与 gas 计量Rust 侧实现集中在 src/lib.rs是理解 Table 扩展如何真正工作的核心。表句柄的确定性生成native_new_table_handlelib.rs 约第 361 行展示了句柄的生成算法以交易哈希为种子拼接当前交易内已创建的表数量转成 4 字节大端序to_be_bytes做 SHA3-256再截取前AccountAddress::LENGTH字节作为TableHandlelet mut digest Sha3_256::new(); let table_len table_data.new_tables.len() as u32; Digest::update(mut digest, table_context.txn_hash); Digest::update(mut digest, table_len.to_be_bytes()); let bytes digest.finalize().to_vec(); let handle AccountAddress::from_bytes(bytes[0..AccountAddress::LENGTH])...;由于交易哈希唯一同一交易内计数器递增因此生成的句柄全局唯一且确定同一交易重放可复现。TableHandle(pub AccountAddress)的Display实现输出T-hex前缀便于日志识别。NativeTableContext 与 TableDataNativeTableContextalib.rs 第 113 行是挂在NativeContextExtensions上的上下文内部持有resolver: a dyn TableResolver——远程存储读取入口txn_hash: [u8; 32]——句柄派生种子table_data: RefCellTableData——本次交易内的可变表数据新建表集合、删除表集合、各表内容RefCell允许在共享上下文上安全地内部可变。每个Table维护content: BTreeMapVecu8, GlobalValue键序列化为字节串值是 VM 层的GlobalValue。读取条目时若缓存未命中会走TableResolver从远程加载并反序列化get_or_create_global_valuelib.rs 第 257 行实现按需懒加载。8 个原生函数的注册table_natives(table_addr, gas_params)lib.rs 第 290 行通过native_functions::make_table_from_iter把 8 个函数注册进原生函数表模块名为table。每个原生函数都有对应的GasParameters结构base、per_byte_serialized等实现按操作计费例如add_box的成本 基础费 键序列化字节数 × 每字节费率 可能的条目加载成本CommonGasParameters::calculate_load_cost区分首次加载、加载失败与缓存命中三种情况。错误码设计原生层用(code 8) category编码错误lib.rs 第 119-126 行常量计算值触发场景ALREADY_EXISTS(100 8) 725607add_box时键已存在NOT_FOUND(101 8) 725863borrow_box/remove_box时键不存在ENOT_EMPTYMove 侧抛出102 8category 由errors::invalid_state编码测试期望 26113destroy_empty时表非空这些值在table_tests.move的#[expected_failure(abort_code ...)]中逐一被验证见下节。会话结束时的变更集输出into_change_set()lib.rs 第 173 行遍历所有表的内容把每个GlobalValue的 effectOp::New / Modify / Delete序列化为字节组装成TableChangeSet { new_tables, removed_tables, changes }交还适配器——这正是集成代码中session.finish_with_extensions()之后要做持久化的数据。测试与验证扩展如何被证明可用扩展目录内同时提供了 Move 侧与 Rust 侧两层测试可以直接作为如何为扩展编写测试的范本。Move 侧功能测试tests/table_tests.move 中的extensions::table_tests模块覆盖了完整的行为矩阵基本读写simple_read_write、simple_update——验证add/borrow/borrow_mut与值更新生命周期test_destroy、test_length——验证remove后计数器递减、空表可destroy_empty错误路径test_insert_fail期望 abort 25607重复键、test_borrow_fail与test_remove_fail期望 abort 25863键不存在、test_destroy_fails期望 abort 26113非空表销毁复杂值类型test_primitiveu64/u128、test_vectorvectoraddress值含borrow_mut后push_back、test_struct自定义Balance结构体表套表test_table_of_tables——Tableaddress, Tableaddress, u128演示表可以作为值嵌入另一张表这是大型状态建模的核心能力资源交互多个用例通过move_to/borrow_global/move_from把表存入账户资源再取回验证表与 Move 资源模型的互操作。Rust 侧驱动tests/move_unit_tests.rs 展示了测试代码如何复刻适配器集成先用all_natives(0x1)加table_natives(0x2, GasParameters::zeros())构造原生函数表与上文集成代码完全同构再以run_move_unit_tests运行包测试并设置 gas 上限 100_000。形式化验证Table 的 Prover 建模sources/Table.spec.move 为 Move Prover 提供了表的抽象规格通过pragma intrinsic map, map_new new, ...把 Table API建模为数学上的 map键值映射并配套spec_new、spec_len、spec_contains、spec_set、spec_remove、spec_get六个规范函数。这意味着依赖 Table 的业务模块可以直接在 Prover 中以 map 语义推理正确性而无需关心底层句柄与序列化细节——这是扩展与 Move 形式化验证体系无缝衔接的体现。小结把扩展接入你的 Move 环境回到third_party/move/extensions/README.md的核心主张Move 扩展机制可以总结为三条实践准则默认关闭按需开启语言核心默认不携带扩展能力CLI/包系统通过 feature如table-extension开启适配器通过显式注册原生函数开启集成 上下文 函数表任何扩展的接入都遵循构造NativeContextExtensions→ 追加原生函数表 →MoveVM::new→new_session_with_extensions→ 结束后取回扩展上下文变更集的固定流程Table 扩展的 README.md 提供了可直接套用的完整 Rust 骨架稳定后并入核心扩展与核心的边界是动态的成熟能力如大规模表在经过验证后有机会转正成为语言标准的一部分。对于希望在自有 Move 环境中获得大规模持久化状态能力的开发者Table 扩展同时提供了语言层 APIextensions::table、原生层实现NativeTableContextTableResolver 8 个原生函数、完备测试与 Prover 规格是理解乃至仿写 Move 扩展机制的最佳参考实现。【免费下载链接】aptos-coreAptos is a layer 1 blockchain built to support the widespread use of blockchain through better technology and user experience.项目地址: https://gitcode.com/GitHub_Trending/ap/aptos-core创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表