ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

fuels-rs 调用响应详解:解构 CallResponse 结构体与合约调用错误处理

fuels-rs 调用响应详解:解构 CallResponse 结构体与合约调用错误处理 fuels-rs 调用响应详解解构 CallResponse 结构体与合约调用错误处理【免费下载链接】fuels-rsFuel Network Rust SDK项目地址: https://gitcode.com/GitHub_Trending/fu/fuels-rs在 fuels-rsFuel Network 官方 Rust SDK中每次合约或脚本调用完成后都会返回一个CallResponse它是你获取返回值、Gas 消耗、交易 ID 和日志的唯一入口。本文基于仓库文档 call-response 展开结合 fuels-programs 与 fuels-core 的源码实现讲清.call().await.unwrap()链式调用的由来、CallResponse各字段的含义与数据来源以及如何用is_ok/is_err/unwrap_err做健壮的错误处理。读完本文你可以独立解读任意一次合约调用的响应并在生产代码中正确捕获与定位合约执行失败的原因。为什么总是链式调用.call().await.unwrap()如果你使用过 fuels-rs一定注意到调用合约方法时几乎总是这样写let response contract_instance.methods().add_one(1).call().await.unwrap();官方文档给出了三个原因.call()与.simulate()二选一.call()是真实上链、会修改区块链状态.simulate()则在不改变状态的前提下模拟执行详见 模拟调用文档。从源码看两者是 CallHandler 上的两个独立方法call()走provider.send_transaction_and_await_commit(tx)真实提交并等待出块而simulate()走provider.dry_run_opt(tx, ...)本地/节点模拟因此你必须显式选择其中一条路径。合约调用是异步的调用返回的是 future你需要.await它也可以借此做并发任务充分利用 Rust 的 async 模型。返回的是Result.call()的签名是ResultCallResponseT右半部分为fuels_core::types::errors::Error所以你需要.unwrap()或更规范的错误传播?来取出CallResponse。此外还有一个文档未展开、但源码中确实存在的第三种形态——提交后不等待CallHandler::submit()见 submit 实现只把交易发上链并立即返回SubmitResponse你可以随时通过它的.response()方法内部调用provider.tx_status(tx_id)轮询状态再换取最终的CallResponse实现见 SubmitResponse。这适合“发射后不管”的提交场景。CallResponse 结构体一次调用的全部产出一旦unwrap出CallResponse你就拿到了 call.rs 中定义的这个结构体/// [CallResponse] is a struct that is returned by a call to the contract or script. Its value /// field holds the decoded typed value returned by the contracts method. The other field holds all /// the receipts returned by the call. #[derive(Clone, Debug)] pub struct CallResponseD { pub value: D, pub tx_status: Success, pub tx_id: OptionTxId, pub log_decoder: LogDecoder, }字段逐一解析value: D—— 持有合约方法返回的值其类型D与 FuelVM 返回的 ABI 类型严格对应合约返回 FuelVM 的u64则D是u64返回元组(u8, bool)则D是(u8, bool)返回 Sway 自定义结构体例如含u64与b256两个字段的MyStructD是编译期生成的 Rust 结构体MyStruct字段分别为u64与[u8; 32]b256在 Rust 中的等价表示。 这个T泛型在调用链上由 ABI 绑定代码确定最终通过T::from_token(token)把解码后的Token转成目标类型见下文get_response。tx_status: Success—— 文档原文中描述的receipts与gas_used在当前源码中嵌套在这个字段里。Success定义于 tx_status.rsreceipts: ArcVecReceipt持有该次合约调用产生的全部收据receiptsFuelVM 执行过程中每一步状态变化都会落到收据上total_gas: u64该次调用消耗的 Gas 总量对应文档里的gas_usedtotal_fee: u64交易总费用。 也就是说文档叙述层面的“receipts字段”“gas_used字段”从源码结构看对应的是tx_status.receipts与tx_status.total_gas。tx_id: OptionTxId—— 对应提交的交易 ID。之所以是Option是因为某些路径如纯模拟在构造响应时可能尚无真实交易 ID真实调用后该字段会被填充。仓库示例中就直接用它反查交易例如 examples/contracts 里的写法let tx_id contract_instance .methods() .initialize_counter(42) .call() .await? .tx_id .unwrap();拿到tx_id后可再交给provider.tx_status(tx_id)或provider.get_transaction_by_id(tx_id)做后续查询tx_id还被用于签名消息证明等场景见 examples/wallets。log_decoder: LogDecoder—— 文档正文没有提及、但源码中存在的第四字段用于把合约log出来的数据解码为 Rust 类型。CallResponse基于它提供了两个便捷方法decode_logs 实现decode_logs_with_type::T()按指定日志类型T解码返回ResultVecTdecode_logs()返回LogResult其results字段是VecResultString表示每条日志解码的成功与否。由于性能开销官方日志文档建议在调试场景之外避免使用decode_logs()。日志的端到端示例可参考 e2e 日志测试 与 日志文档。value是怎么从收据里“变”出来的CallResponse并非凭空构造其组装逻辑在 CallHandler::get_response/// Create a [CallResponse] from TxStatus pub fn get_response(self, tx_status: TxStatus) - ResultCallResponseT { let success tx_status.take_success_checked(Some(self.log_decoder))?; let token self.call .parse_call(success.receipts, self.decoder_config, T::param_type())?; Ok(CallResponse { value: T::from_token(token)?, log_decoder: self.log_decoder.clone(), tx_id: self.cached_tx_id, tx_status: success, }) }调用链可以概括为call()→build_tx()构建脚本交易 → 节点执行返回TxStatus→take_success_checked校验成功状态 → 从success.receipts中解析出Token→T::from_token反序列化为value。对于多调用场景multicallsget_response 的 Vec 版本 会用ReceiptParser按contract_id逐个解析每个调用并把结果打包成Token::Tuple对应文档 multicalls 中把类型标注移到调用方法上的用法。错误处理is_ok / is_err / unwrap_err合约调用返回的Result遵循标准 Rust 语义可以用is_ok和is_err检查一次调用是Ok还是携带错误两者返回true/falselet is_ok response.is_ok(); let is_error response.is_err();当is_err返回true时用unwrap_err取出错误信息if response.is_err() { let err response.unwrap_err(); println!(ERROR: {:?}, err); };错误从哪来TxStatus 的校验与 revert 解码unwrap_err取出的Error不是笼统的字符串。从源码看合约调用失败主要经由 TxStatus::take_success_checked 被转换为Err交易被挤出SqueezedOut映射为Error::Transaction(Reason::SqueezedOut(reason))执行失败Failure / PreconfirmationFailure进入map_revert_error实现见 tx_status.rs它会利用合约 ABI 中的 revert 元数据把裸的revert_id翻译成可读的 Sway 层信息例如require/ 自定义 revert id解码为panicked at: \{pkg} - {file}:{line}:{col} with message {msg}并尝试还原日志内容assert_eq/assert_ne生成assertion failed: (left right)及左右两边的 Debug 值发送消息、转账地址失败等信号量也有对应文案。交易尚未上链Submitted返回transactions was not yet included这提示你此时应改用SubmitResponse轮询模式而非立刻断言成功。错误的具体类型定义在 fuels-core 的 errors 模块Error::Transaction(Reason::...)之下有Builder、Validation、SqueezedOut、Failure { reason, revert_id, receipts }、Other五个Reason变体。其中Failure变体保留了完整的receipts即使交易失败你仍可通过err中的收据逐条排查 VM 执行到失败位置为止的全部轨迹——这与CallResponse::tx_status.receipts在成功路径上提供的调试能力是对称的。小结从一次.call()到完整可观测性需求入口取合约返回值response.value取全部收据 / Gas / 费用response.tx_status.receipts/total_gas/total_fee反查链上交易response.tx_id→provider.tx_status/get_transaction_by_id解码合约日志response.decode_logs_with_type::T()/decode_logs()判断成败并取错误is_ok/is_err/unwrap_err失败时Reason::Failure内含 revert 位置、消息与收据掌握以上内容后你就具备了对 fuels-rs 合约调用做完整“响应侧”处理的能力用.call()与.simulate()选择执行路径从CallResponse的四个字段提取返回值、执行统计、交易 ID 与日志并在失败时借助unwrap_err拿到的结构化错误含 Sway 源码位置与收据精确定位问题。若要进一步定制提交前的交易构建或事后解码脚本交易可继续参考 自定义交易构建器 与 解码脚本交易 两篇文档。【免费下载链接】fuels-rsFuel Network Rust SDK项目地址: https://gitcode.com/GitHub_Trending/fu/fuels-rs创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表