与预言机(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.tomllinera-sdk/tests/fixtures/meta-counter/ ├── Cargo.toml # 定义 meta_counter_contract 与 meta_counter_service 两个二进制 ├── README.md └── src/ ├── lib.rs # ABIOperation / Message 类型定义 ├── contract.rs # 合约实现 └── service.rs # 服务实现其中 Cargo.toml 声明的两个 bin 目标meta_counter_contract→src/contract.rsmeta_counter_service→src/service.rs这正是 Linera 应用的标准形态合约与服务编译为两个独立的 Wasm 模块通过同一个 ABI 相连。值得注意的是该 crate 把counter即 examples/counter 中的计数合约作为直接依赖并通过linera-sdk.workspace true引用工作区 SDK——这是它能调用另一个应用的前提。二、ABI 层Operation 与 Message 如何描述一次跨应用交互src/lib.rs 定义了MetaCounterAbi同时实现了ContractAbi与ServiceAbipub 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-counterOperation是用户通过链上区块提交的指令它的字段几乎一一对应了 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)默认authenticatedfalse、is_trackedfalse、fuel_grant0仅允许选择是否附带服务查询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的ApplicationIdimpl MetaCounterContract { fn counter_id(mut self) - ApplicationIdcounter::CounterAbi { self.runtime.application_parameters() } }也就是说meta-counter 部署时不需要硬编码目标应用的 ID而是在CreateApplication时通过参数传入详见第六节的测试证据。这也对应了合约的Parameters关联类型type Message Message; type InstantiationArgument (); type Parameters ApplicationIdcounter::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(bannouncements.to_vec()), instantiated.to_string(), ); self.runtime.send_message(this_chain, Message::Increment(0)); }这里展示了三个 SDK 能力参数校验调用counter_id()只是为了确认参数可解析若参数配置错误初始化直接失败事件流event streamself.runtime.emit(StreamName(bannouncements.to_vec()), instantiated.to_string())向名为announcements的事件流写入一条事件。在 linera-core/src/unit_tests/wasm_client_tests.rs 中可以看到测试断言创建 meta-counter 的区块事件恰好包含一条announcements流上值为instantiated的事件初始化即发消息向自身链发送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.rsprepare_message与 同文件 L499-L517with_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-L207message_origin_timestamp()返回消息在源链区块上的时间戳合约据此断言来源时间不能晚于当前系统时间防止未来消息对应实现见 runtime.rs L219-L223Message::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调用目标应用再反序列化响应返回。四、服务实现把预言机查询转发给 countersrc/service.rs 非常简短但点明了预言机查询的服务端路径impl Service for MetaCounterService { type Parameters ApplicationIdcounter::CounterAbi; async fn new(runtime: ServiceRuntimeSelf) - 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_servicetrue并断言执行后的区块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}));这段测试验证了三条事实区块中确实出现了oracle_responses且类型为OracleResponse::Service(json)——即服务查询型预言机响应查询执行于区块构建阶段在跨链消息送达接收链之前所以读到的是 counter 当前的初始值10而不是后来Increment(5)的结果随后接收链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_testswasm_client_tests.rs 先发布counter与meta-counter两个模块并绑定 ABI 类型let module_id2 publisher.publish_wasm_example(meta-counter).await?; let module_id2 module_id2.with_abi::meta_counter::MetaCounterAbi, ApplicationIdCounterAbi, ()();创建应用时L321-L329CreateApplication的参数即 counter 的应用 ID、required_application_ids里也声明了对 counter 的依赖——这正是合约里application_parameters()能拿到ApplicationIdcounter::CounterAbi的来源。随后测试覆盖了三条核心路径跨应用调用 预言机L342-L376Operation::increment(receiver_id, 5, true)配合fuel_grant 1000000验证区块 oracle 响应与最终 counter 值未追踪消息失败 → 回弹L379-L403Operation::fail(receiver_id)产生的消息在接收链上执行失败panic接收链因此拒绝该入站消息incoming_bundles[0].action MessageAction::Reject消息弹回源链追踪消息失败L406 起operation.is_tracked true的失败消息验证追踪模式下的送达/失败处理。6.2 工作线程级测试wasm_worker_testswasm_worker_tests.rs 展示了更底层的操作直接加载两个模块的字节码L283-L298、把 blobs 写入 worker 存储L310-L320、发布模块L322-L338、创建 counter 与 meta-counter 应用L340-L390meta-counter 的parameters为 counter 的ApplicationId、required_application_ids为[counter_app_id]最后通过Operation::fail验证失败消息的传递L392 起。这些测试与客户端测试互为印证覆盖了内存/服务/rocksdb/scylladb多种存储后端的相同语义见 wasm_client_tests.rs L222-L260 的四个变体。七、总结meta-counter 揭示的 Linera SDK 关键 API 清单以这个夹具为窗口可以梳理出 Linera SDK 合约侧的核心编程模型能力调用方式源码位置读取应用参数获知被调用方 IDruntime.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 L311meta-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),仅供参考