news 2026/9/11 9:14:12

深度解析 Linera meta-counter 测试夹具:跨应用调用(Cross-Application Calls)与预言机(Oracle)机制

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
深度解析 Linera meta-counter 测试夹具:跨应用调用(Cross-Application Calls)与预言机(Oracle)机制

深度解析 Linera meta-counter 测试夹具:跨应用调用(Cross-Application Calls)与预言机(Oracle)机制

【免费下载链接】linera-protocolMain repository for the Linera protocol项目地址: https://gitcode.com/GitHub_Trending/li/linera-protocol

导读

meta-counter 是 Linera 协议仓库中一个极其精简但作用关键的测试应用(fixture)。根据它的官方 README,该应用唯一的存在目的就是"测试跨应用调用(cross-application calls)与预言机(oracles)"。本文以 linera-sdk/tests/fixtures/meta-counter/README.md 为核心骨架,结合其完整的合约、服务与 ABI 实现,以及 linera-core 中的集成测试,系统讲解 Linera SDK 中跨应用调用、预言机查询、跨链消息投递、消息回弹(bouncing)与燃料授权等底层机制。读完本文,你将掌握 Linera 应用之间如何互相调用、如何发起被区块记录的服务查询,以及这些能力在官方测试中是如何被验证的。


一、这个"只有一句话"的 README 背后是什么

linera-sdk/tests/fixtures/meta-counter/README.md全文只有一句:

This application is only used for testing cross-application calls and oracles.

它的定位是Linera SDK 测试夹具(fixture),不面向生产使用,而是作为测试"应用调用应用"这条核心链路的载体。meta-counter 本身没有状态,它的全部逻辑就是:接收外部操作(Operation),把操作转换成发给另一个应用(counter)的调用或消息,从而在测试中检验 Linera 的以下核心能力:

  • 跨应用调用(cross-application call):一个合约通过call_application同步调用另一个合约的操作;
  • 预言机(oracle):合约通过query_service向另一个应用的服务发起 GraphQL 查询,查询结果被记录在区块的oracle_responses中;
  • 跨链消息(cross-chain message):通过send_message把消息投递到目标链;
  • 消息投递控制:认证(authentication)、追踪(tracking)、燃料授权(fuel grant);
  • 消息回弹(bouncing):消息被目标链拒绝后弹回原链的处理语义。

这个目录虽然 README 简短,但代码结构完整,共三个源文件加一个Cargo.toml

linera-sdk/tests/fixtures/meta-counter/ ├── Cargo.toml # 定义 meta_counter_contract 与 meta_counter_service 两个二进制 ├── README.md └── src/ ├── lib.rs # ABI:Operation / Message 类型定义 ├── contract.rs # 合约实现 └── service.rs # 服务实现

其中 Cargo.toml 声明的两个 bin 目标:

  • meta_counter_contractsrc/contract.rs
  • meta_counter_servicesrc/service.rs

这正是 Linera 应用的标准形态:合约与服务编译为两个独立的 Wasm 模块,通过同一个 ABI 相连。值得注意的是该 crate 把counter(即 examples/counter 中的计数合约)作为直接依赖,并通过linera-sdk.workspace = true引用工作区 SDK——这是它能"调用另一个应用"的前提。


二、ABI 层:Operation 与 Message 如何描述一次跨应用交互

src/lib.rs 定义了MetaCounterAbi,同时实现了ContractAbiServiceAbi

pub struct MetaCounterAbi; impl ContractAbi for MetaCounterAbi { type Operation = Operation; type Response = (); } impl ServiceAbi for MetaCounterAbi { type Query = Request; type QueryResponse = Response; }

2.1 Operation:用户如何驱动 meta-counter

Operation是用户通过链上区块提交的"指令",它的字段几乎一一对应了 Linera SDK 消息投递的每一项能力开关:

#[derive(Debug, Serialize, Deserialize)] pub struct Operation { pub recipient_id: ChainId, // 消息接收链 pub authenticated: bool, // 是否携带调用者认证 pub is_tracked: bool, // 是否启用消息追踪(用于跨链送达确认) pub query_service: bool, // 是否在发送前做一次服务预言机查询 pub fuel_grant: u64, // 为消息额外授予的 wasm_fuel pub message: Message, // 实际要投递的消息体 }

同时提供了两个便捷构造器:

  • Operation::increment(recipient_id, value, query_service):构造一条Message::Increment(value),默认authenticated=falseis_tracked=falsefuel_grant=0,仅允许选择是否附带服务查询;
  • Operation::fail(recipient_id):构造一条Message::Fail,用于测试"消息在目标链上执行失败(panic)"的场景。

2.2 Message:跨链传递的载荷

#[derive(Debug, Serialize, Deserialize)] pub enum Message { Increment(u64), Fail, }

Increment(u64)会被转发成对 counter 应用的CounterOperation::Increment { value }调用;Fail则会在执行时故意panic!,用于验证失败消息的处理语义(见下文第四节)。


三、合约实现:instantiate / execute_operation / execute_message 三段式

src/contract.rs 实现了Contracttrait。它最值得注意的一点是:meta-counter 通过"应用参数(application parameters)"获得被调用应用(counter)的ApplicationId

impl MetaCounterContract { fn counter_id(&mut self) -> ApplicationId<counter::CounterAbi> { self.runtime.application_parameters() } }

也就是说,meta-counter 部署时不需要硬编码目标应用的 ID,而是在CreateApplication时通过参数传入(详见第六节的测试证据)。这也对应了合约的Parameters关联类型:

type Message = Message; type InstantiationArgument = (); type Parameters = ApplicationId<counter::CounterAbi>; type EventValue = String;

3.1 instantiate:初始化即验证 + 自投消息 + 事件流

async fn instantiate(&mut self, _argument: ()) { // Validate that the application parameters were configured correctly. self.counter_id(); // 向自身发送一条 no-op 消息,用于测试"初始化时发送消息"的合约。 let this_chain = self.runtime.chain_id(); self.runtime.emit( StreamName(b"announcements".to_vec()), &"instantiated".to_string(), ); self.runtime.send_message(this_chain, Message::Increment(0)); }

这里展示了三个 SDK 能力:

  1. 参数校验:调用counter_id()只是为了确认参数可解析,若参数配置错误,初始化直接失败;
  2. 事件流(event stream)self.runtime.emit(StreamName(b"announcements".to_vec()), &"instantiated".to_string())向名为announcements的事件流写入一条事件。在 linera-core/src/unit_tests/wasm_client_tests.rs 中可以看到测试断言:创建 meta-counter 的区块事件恰好包含一条announcements流上值为"instantiated"的事件;
  3. 初始化即发消息:向自身链发送Message::Increment(0),因为值为 0,所以不会改变 counter 的值——专门用来覆盖"合约在 instantiate 阶段发送消息"的代码路径。

3.2 execute_operation:把 Operation 变成一条受控的消息

async fn execute_operation(&mut self, operation: Operation) { // 操作执行时,origin timestamp 必须为空 assert!( self.runtime.message_origin_timestamp().is_none(), "Origin timestamp must not be set when executing an operation" ); let Operation { recipient_id, authenticated, is_tracked, query_service, fuel_grant, message } = operation; let mut message = self.runtime.prepare_message(message).with_grant(Resources { wasm_fuel: fuel_grant, ..Resources::default() }); if authenticated { message = message.with_authentication(); } if is_tracked { message = message.with_tracking(); } if query_service { // 预言机查询:结果会被记录进区块 let counter_id = self.counter_id(); self.runtime.query_service(counter_id, "query { value }".into()); } message.send_to(recipient_id); }

这一方法集中展示了 Linera 的**消息构建器(MessageBuilder)**模式:

  • prepare_message(message)创建构建器,with_grant(Resources { wasm_fuel, .. })为消息授予燃料配额;
  • with_authentication()让消息携带调用者的认证信息(接收方可通过authenticated_caller_id/ 账户权限检查确认来源);
  • with_tracking()启用消息追踪;
  • query_service为真,则在发送前执行一次预言机查询(详见第五节);
  • 最后send_to(recipient_id)把消息投递到目标链。

这些 API 的源码实现在 linera-sdk/src/contract/runtime.rs(prepare_message)与 同文件 L499-L517(with_tracking/with_authentication/with_grant/send_to)。

3.3 execute_message:处理收到的消息(含回弹与失败语义)

async fn execute_message(&mut self, message: Message) { let is_bouncing = self.runtime.message_is_bouncing() .expect("Message delivery status has to be available when executing a message"); let origin_timestamp = self.runtime.message_origin_timestamp() .expect("Origin timestamp has to be available when executing a message"); assert!(origin_timestamp <= self.runtime.system_time(), "Origin timestamp must not be in the future"); if is_bouncing { log::trace!("receiving a bouncing message {message:?}"); return; } match message { Message::Fail => { panic!("Message failed intentionally"); } Message::Increment(value) => { let counter_id = self.counter_id(); let operation = counter::CounterOperation::Increment { value }; self.runtime.call_application(true, counter_id, &operation); } } }

这里是消息语义的核心展示:

  • message_is_bouncing():返回true表示该消息此前被目标链拒绝、如今"弹回"原链。对弹回的消息直接返回,不做业务处理。对应 SDK 实现见 runtime.rs L203-L207;
  • message_origin_timestamp():返回消息在源链区块上的时间戳,合约据此断言"来源时间不能晚于当前系统时间",防止未来消息;对应实现见 runtime.rs L219-L223;
  • Message::Fail分支故意panic!("Message failed intentionally")——一个未追踪的失败消息会导致目标链拒绝该入站消息,从而触发"回弹"机制;而Message::Increment分支则是真正的跨应用调用self.runtime.call_application(true, counter_id, &operation)以"已认证"方式同步调用 counter 应用的操作。

跨应用调用的 SDK 实现见 runtime.rs L291-L308:它把Operation序列化后通过contract_wit::try_call_application调用目标应用,再反序列化响应返回。


四、服务实现:把预言机查询"转发"给 counter

src/service.rs 非常简短,但点明了预言机查询的服务端路径

impl Service for MetaCounterService { type Parameters = ApplicationId<counter::CounterAbi>; async fn new(runtime: ServiceRuntime<Self>) -> Self { MetaCounterService { runtime } } async fn handle_query(&self, request: Request) -> Response { let counter_id = self.runtime.application_parameters(); self.runtime.query_application(counter_id, &request) } }

当合约端执行runtime.query_service(counter_id, "query { value }".into())时,运行时会在当前链的上下文内启动 counter 的 service,把 GraphQL 请求转发给它;meta-counter 的 service 又把请求二次转发给 counter 应用的服务(query_application),从而读取 counter 的内部视图值。


五、预言机(Oracle)机制:查询结果如何进入区块

预言机是 Linera 的一个重要设计:合约可以发起"出链"查询,查询结果由验证者执行并写入区块,成为区块的一部分(oracle_responses),从而保证确定性。

在 wasm_client_tests.rs 中,测试构造了Operation::increment(receiver_id, 5, true)query_service=true),并断言执行后的区块:

let responses = &block.body.oracle_responses; let [_, responses] = &responses[..] else { panic!("Unexpected oracle responses: {responses:?}"); }; let [OracleResponse::Service(json)] = &responses[..] else { ... }; let response_json = serde_json::from_slice::<serde_json::Value>(json).unwrap(); assert_eq!(response_json["data"], json!({"value": 10}));

这段测试验证了三条事实:

  1. 区块中确实出现了oracle_responses,且类型为OracleResponse::Service(json)——即服务查询型预言机响应;
  2. 查询执行于区块构建阶段(在跨链消息送达接收链之前),所以读到的是 counter 当前的初始值10,而不是后来Increment(5)的结果;
  3. 随后接收链process_inbox处理消息、counter 被call_application递增 5 后,对 meta-counter 服务的查询{ value }才返回{"value": 5}(见同文件 L360-L376)。两次查询结果10vs5的差异,精确地体现了"预言机查询发生在区块内、跨链消息执行发生在后续区块"的时序。

而合约端的发起方式正是execute_operation中的:

self.runtime.query_service(counter_id, "query { value }".into());

其 SDK 实现在 runtime.rs L358。


六、测试证据:meta-counter 如何在官方测试中被使用

6.1 客户端级集成测试(wasm_client_tests)

wasm_client_tests.rs 先发布countermeta-counter两个模块,并绑定 ABI 类型:

let module_id2 = publisher.publish_wasm_example("meta-counter").await?; let module_id2 = module_id2.with_abi::<meta_counter::MetaCounterAbi, ApplicationId<CounterAbi>, ()>();

创建应用时(L321-L329),CreateApplication的参数即 counter 的应用 ID、required_application_ids里也声明了对 counter 的依赖——这正是合约里application_parameters()能拿到ApplicationId<counter::CounterAbi>的来源。

随后测试覆盖了三条核心路径:

  • 跨应用调用 + 预言机(L342-L376):Operation::increment(receiver_id, 5, true)配合fuel_grant = 1000000,验证区块 oracle 响应与最终 counter 值;
  • 未追踪消息失败 → 回弹(L379-L403):Operation::fail(receiver_id)产生的消息在接收链上执行失败(panic),接收链因此拒绝该入站消息,incoming_bundles[0].action == MessageAction::Reject,消息弹回源链;
  • 追踪消息失败(L406 起):operation.is_tracked = true的失败消息,验证追踪模式下的送达/失败处理。

6.2 工作线程级测试(wasm_worker_tests)

wasm_worker_tests.rs 展示了更底层的操作:直接加载两个模块的字节码(L283-L298)、把 blobs 写入 worker 存储(L310-L320)、发布模块(L322-L338)、创建 counter 与 meta-counter 应用(L340-L390,meta-counter 的parameters为 counter 的ApplicationIdrequired_application_ids[counter_app_id]),最后通过Operation::fail验证失败消息的传递(L392 起)。这些测试与客户端测试互为印证,覆盖了"内存/服务/rocksdb/scylladb"多种存储后端的相同语义(见 wasm_client_tests.rs L222-L260 的四个变体)。


七、总结:meta-counter 揭示的 Linera SDK 关键 API 清单

以这个夹具为窗口,可以梳理出 Linera SDK 合约侧的核心编程模型:

能力调用方式源码位置
读取应用参数(获知被调用方 ID)runtime.application_parameters()contract.rs
跨应用同步调用runtime.call_application(authenticated, app_id, &operation)runtime.rs L291
预言机服务查询runtime.query_service(app_id, query)runtime.rs L358
发送跨链消息runtime.send_message(chain_id, msg)/prepare_message(msg).send_to(...)runtime.rs L244-L255
消息认证 / 追踪 / 燃料授权with_authentication()/with_tracking()/with_grant(Resources)runtime.rs L499-L517
检测消息回弹runtime.message_is_bouncing()runtime.rs L203
读取消息来源时间戳runtime.message_origin_timestamp()runtime.rs L219
写事件流runtime.emit(StreamName, &value)runtime.rs L311

meta-counter 虽然只是 linera-sdk/tests/fixtures 下的一个测试用小程序,但它用最少的代码同时覆盖了 Linera 应用互联中最重要的三类能力——应用间调用、预言机查询与跨链消息的完整生命周期。如果你的目标是编写自己的多合约应用,这个夹具连同 examples/counter 是一份非常值得对照阅读的"最小可运行参照":它清楚地展示了Parameters如何充当"依赖注入"、Operation如何映射到消息投递选项、以及失败消息在 Linera 的"拒绝—回弹"模型中如何被处理。

【免费下载链接】linera-protocolMain repository for the Linera protocol项目地址: https://gitcode.com/GitHub_Trending/li/linera-protocol

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/11 9:14:07

如何查找空白符号、查看Unicode编码

适用情况示例&#xff1a; 游戏&#xff08;如王者荣耀&#xff09;中ID被占用&#xff0c;可以添加一个空白符号无痛重名 让微信朋友圈的分隔符显得更高级&#xff08;&#xff1f; 网上找的符号太容易重复、看不懂、复制别人的不知道到底复制了个什么东西&#xff1f; 这篇文…

作者头像 李华
网站建设 2026/9/11 9:13:36

UART传输时间精确计算:从7N1到115200波特率的实操指南

1. 这不是“背公式”问题&#xff0c;而是搞懂UART时序本质的实操门槛你手头正调试一块STM32开发板&#xff0c;串口打印突然卡顿&#xff1b;或者用FT231X转USB调试ESP32&#xff0c;发现发出去的JSON字符串总在第7个字节后被截断&#xff1b;又或者在Linux下用stty配置串口&a…

作者头像 李华
网站建设 2026/9/11 9:11:11

WorkBuddy开放平台实战:从零构建自动周报Agent的完整指南

1. WorkBuddy 开放平台到底解决了什么问题1.1 为什么个人开发者需要 WorkBuddy先把一个现实摊开讲&#xff1a;个人开发者做一个 Agent 应用&#xff0c;真正耗时间的往往不是“写提示词”&#xff0c;而是把一串散落的系统拼起来。模型调用、工具函数、上下文管理、会话记忆、…

作者头像 李华
网站建设 2026/9/11 9:10:59

Natural Earth 110m 数据与经纬网格:从 GeoPandas 到 Web 地图投影实践

简介&#xff1a;这份压缩包提供一套基于Natural Earth 110m比例尺的全球物理地图底图&#xff0c;面向GIS分析、环境研究与地图制图用户&#xff0c;尤其适合需要标准世界地理底图的项目与课堂场景&#xff0c;可帮助快速搭建空间数据基础框架。其中shp/dbf/shx构成几何与属性…

作者头像 李华