news 2026/9/14 16:28:39

Solana 链上程序测试分层指南:从 AsyncClient/SyncClient 到 Runtime 单元测试

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Solana 链上程序测试分层指南:从 AsyncClient/SyncClient 到 Runtime 单元测试

Solana 链上程序测试分层指南:从 AsyncClient/SyncClient 到 Runtime 单元测试

【免费下载链接】solanaWeb-Scale Blockchain for fast, secure, scalable, decentralized apps and marketplaces.项目地址: https://gitcode.com/GitHub_Trending/so/solana

本篇围绕 Solana 仓库中的设计文档 testing-programs.md 展开,讲清楚“应用提交交易却收不到预期结果”这一类故障的分层排查思路:为什么要把测试目标从整条集群逐层下移到 TPU、Bank、Runtime,以及AsyncClient/SyncClient两大 trait 是如何贯穿各层客户端实现的。读完本文,你可以结合 sdk/src/client.rs、client/src/thin_client.rs、banks-client/src/lib.rs 与 program-test/src/lib.rs 中的源码,为链上程序选择错误面最小、反馈最快的测试层级。

一、为什么集群上的失败难以定位

应用把交易发送到 Solana 集群,再向验证者查询确认结果。文档首先指出:当集群行为不符合预期时,原因可能来自多个层次,而非程序本身:

  • 程序本身有 bug(The program is buggy)
  • BPF loader 拒绝了不安全的程序指令
  • 交易体积过大
  • 交易本身无效
  • Runtime 在执行某笔交易时,另一笔交易正在访问同一账户(并发冲突)
  • 网络丢掉了这笔交易
  • 集群回滚了账本(ledger rollback)
  • 验证者对查询返回了恶意响应

这 8 类原因覆盖了从“程序逻辑”到“网络传输”再到“共识/账本”的完整故障谱系。问题在于:如果你只对着最终集群写测试,所有这些错误源会混在一起,无法区分“我的程序错了”还是“环境在骗我”。文档给出的解法是一条清晰的分层原则:排障时应把测试目标切换到更底层、错误面更少的组件(retarget a lower-level component, where fewer errors are possible),通过替换AsyncClient/SyncClient的不同实现来完成这种“换层”。

二、AsyncClient 与 SyncClient 两大 trait

文档给出的核心抽象是两个 trait(示意版本):

trait AsyncClient { fn async_send_transaction(&self, transaction: Transaction) -> io::Result<Signature>; } trait SyncClient { fn get_signature_status(&self, signature: &Signature) -> Result<Option<transaction::Result<()>>>; }

语义是:异步接口“发出即返回”,只负责把签名后的交易送出去;同步接口负责“等结果、查状态”,并且按预期要自己完成重试、刷新 blockhash、重新签名。

当前仓库中这对 trait 的正式定义位于 sdk/src/client.rs,比文档示意版本更完整,值得逐项核对:

  • SyncClient(见 sdk/src/client.rs#L34-L174):除了文档点名的get_signature_status,还包含send_and_confirm_message(“创建交易并发送,按需重试”)、transfer_and_confirmget_account/get_balance(支持CommitmentConfig承诺级别)、get_latest_blockhashis_blockhash_validpoll_for_signature_confirmation等。文档对它的定义是“synchronous implementations are expected to create transactions, sign them, and send them with multiple retries, updating blockhashes and resigning as-needed”——即同步实现自带完整的重试与重签循环。
  • AsyncClient(见 sdk/src/client.rs#L176-L241):核心方法是async_send_versioned_transaction,其余async_send_transactionasync_send_batchasync_send_messageasync_send_instructionasync_transfer都带默认实现,逐层收敛到“发出去就不管了”。
  • 二者合一的Clienttrait(sdk/src/client.rs#L30-L32)只是额外要求暴露tpu_addr(),方便测试代码知道自己在打谁。

这个抽象是整个测试分层的“插座标准”:上层的测试代码只依赖 trait 方法,下层组件只要能实现这两个 trait 就能被塞进同一套测试逻辑里,这正是“retarget”能成立的前提。

三、ThinClient:面向整条集群的最高层实现

文档把ThinClient定位为“最高层实现”:它打向一条真正的 Solana 集群——可以是已部署的 testnet,也可以是本机跑的本地集群。

当前仓库中该实现位于 client/src/thin_client.rs,几个关键细节可以与文档逐一对上:

  1. 构造方式ThinClient::new(rpc_addr, tpu_addr, connection_cache)(client/src/thin_client.rs#L136-L142)分别绑定一个 RPC 地址和一个 TPU 地址;多节点场景可用new_from_addrs传入等长的 RPC/TPU 地址数组。
  2. 异步发送:其AsyncClient实现(client/src/thin_client.rs#L627-L656)用bincode序列化交易后经连接池的send_data直接发到 TPU,并立刻返回signatures[0],完全不等待服务器接受——与AsyncClient的语义定义完全一致。
  3. 同步确认send_and_confirm_transaction(client/src/thin_client.rs#L214-L261)展示了“同步实现要自己重试”的具体形态:在MAX_PROCESSING_AGE窗口内反复重发同一交易、轮询签名确认,重试耗尽后get_latest_blockhash换新 blockhash 并重新签名;SyncClient默认实现里的send_and_confirm_message固定以tries = 5调用它(client/src/thin_client.rs#L347-L356)。
  4. 多连接择优ClientOptimizer(client/src/thin_client.rs#L46-L111)会对多个 RPC 连接做“实验—上报—取最优点”的轮询式负载均衡,测试里有对应单测test_client_optimizer验证择优逻辑。
  5. 现状提示:该结构体目前标注#[deprecated(since = "1.19.0", note = "Use [RpcClient] or [TpuClient] instead.")](client/src/thin_client.rs#L113-L114)。也就是说,文档时代的“最高层 ThinClient”在新代码中已被拆解:查询类走RpcClient,发送类走TpuClient。阅读这份实现仍有助于理解集群层测试的错误面构成(RPC 查询 + UDP/QUIC 直发 TPU 两种路径),但新写测试代码时应以RpcClient/TpuClient为准。

ThinClient层测试的代价,正是第一节列出的网络、回滚、恶意验证者等错误源全部在场;它的价值则在于“端到端最真实”。

四、TpuClient:跳过 RPC 查询、直发 TPU 的一层

文档在 TPU 层写道:应用通过 Rust channel 发送交易,“没有网络队列或丢包带来的意外”,且 TPU 层保留全部“正常”交易错误——做签名校验、可能报 account-in-use、结果落入带 PoH 哈希的账本。文档当时标注“尚未实现”。

以当前仓库为准,这一层已经落地,入口是 client/src/tpu_client.rs:

  • TpuClient结构(client/src/tpu_client.rs#L31-L40)是solana_tpu_client后端客户端的薄包装,支持 UDP 与 QUIC 两种连接池(TpuClientWrapper枚举同时持有QuicUdp两个变体)。
  • 构造方式TpuClient::new(rpc_client, websocket_url, config)(client/src/tpu_client.rs#L80-L97):用 RPC 客户端确定当前 leader,用 websocket 订阅节点切换,再向“当前及即将上任”的 leader TPU 扇出发送。
  • 发送接口send_transaction/try_send_transaction_batch等按fanout槽位数扇出(DEFAULT_FANOUT_SLOTS/MAX_FANOUT_SLOTS在 client/src/tpu_client.rs#L19-L22 中导出)。

从源码结构看,这一层恰好实现了文档的设计意图:把“网络是否收到”这一变量收敛为“确定性扇出到已知 leader”,从而把排查范围从第 1 节的 8 类原因压缩到签名校验、账户冲突、交易本身无效这几类 TPU 层错误。

五、BankClient(BanksClient):Bank 层测试

文档下一层是 Bank:“Bank 不做签名校验、不生成账本,是测试新链上程序的便利层;可以在 native 程序实现与 BPF 编译产物之间切换;Bank 的 API 是同步的。”

当前仓库中对应solana-banks-client,入口为 banks-client/src/lib.rs:

  • 文件头注释直接说明了定位:“A client for the ledger state, from the perspective of an arbitrary validator”,建议用start_tcp_client()创建客户端(基于 tarpc 的 TCP 传输,见 banks-client/src/lib.rs#L1-L11)。
  • BanksClient结构(banks-client/src/lib.rs#L45-L48)暴露的方法与文档描述吻合:send_transactionprocess_transaction_with_commitment_and_context(同步拿transaction::Result<()>)、process_transaction_with_preflight_and_commitment_and_context(带预检模拟结果BanksTransactionResultWithSimulation)、process_transaction_with_metadata_and_context(带日志/单元消耗等元数据BanksTransactionResultWithMetadata)、以及get_transaction_statusget_slot等查询。
  • 服务端则由 banks-server 在进程内持有BankForks,因此所谓“Bank 层测试”实际上是把生产路径里 Bank 之上的组件(TPU、PoH、共识)全部摘掉。

Bank 内部真正的处理入口是 runtime/src/bank.rs 中的Bank::process_transaction(runtime/src/bank.rs#L5735,批量版本process_transactions在同文件 L7748)。这意味着 BanksClient 测试打到的,与验证者实际执行交易的函数是同一个——只是去掉了签名校验、网络与共识带来的非确定性。

文档强调的另一个特性——“在 native 实现与 BPF 编译产物间切换”——在仓库中有直接落点:program-test/src/lib.rs 依赖solana_bpf_loader_program::serialization::serialize_parameters等 BPF loader 能力来加载编译后的 SBF 程序,同时默认路径下可运行原生 builtin 函数(invoke_builtin_function,program-test/src/lib.rs#L104-L120)。同一份测试代码因此可以既跑 native 快速路径,也跑 BPF 编译产物做等价验证。

六、Runtime 单元测试:最短的编辑-编译-运行循环

文档最底层指出:Bank 之下是 Runtime,它是单元测试的理想环境——把 Runtime 静态链接进原生程序实现后,开发者获得最短的 edit-compile-run 循环;没有动态链接,堆栈里带调试符号,程序错误直接可查。

在仓库中,这一思想被 program-test 工具化:

  • program-test/src/lib.rs#L1 的模块注释自述:“provides a BanksClient-based test framework SBF programs”——即它在一个测试进程里拉起Bank/BankForks与本地 banks server,再用BanksClient与之对话,把第五节的 Bank 层测试打包成了开箱即用的宏与 builder(ProgramTest等)。
  • 依赖链上它直接引用solana_runtime::bank::Bankbank_forks::BankForksgenesis_utils(program-test/src/lib.rs#L23-L29),印证了“Runtime 静态链接进测试进程”这一设计:测试二进制里同时包含被测程序的 native 入口与 Bank 执行栈,出错时堆栈可贯穿到 Runtime 内部。
  • program-test/tests/ 目录下的cpi.rslamports.rsrealloc.rsreturn_data.rscompute_units.rswarp.rs等用例,覆盖了跨程序调用、余额变更、内存重分配、return data、compute unit 计量与槽位 warp 等程序测试的典型场景,可作为编写程序测试的现成参照。

七、选择测试层级的实用结论

把文档的分层脉络与当前仓库实现并置,可以得到一张“错误面递减、真实性也递减”的选型表:

测试层级仓库入口覆盖的错误源适合场景
整条集群(ThinClient 时代,现由 RpcClient+TpuClient 接替)client/src/thin_client.rs全部 8 类,含网络、回滚、恶意验证者端到端验收、升级演练
TPU 层client/src/tpu_client.rs签名校验、账户冲突、交易无效、PoH 入账验证发送路径与扇出行为
Bank 层banks-client/src/lib.rs仅交易处理错误,无签名校验与网络新程序功能测试,native/BPF 双跑
Runtime 层program-test/src/lib.rs进程内确定环境,带调试符号单元测试,最短编辑-编译-运行循环

文档给出的方法论一句话总结:先用 Runtime/program-test 把程序逻辑钉死,再用 BanksClient 验证 Bank 语义,必要时才上升到 TPU 与集群层——每一层都通过AsyncClient/SyncClient这对 trait 保持同一套调用方式,使“换层”只改实现、不改测试逻辑。

【免费下载链接】solanaWeb-Scale Blockchain for fast, secure, scalable, decentralized apps and marketplaces.项目地址: https://gitcode.com/GitHub_Trending/so/solana

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

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

HarmonyOS时钟应用开发:角度计算与动画实现

1. 项目概述&#xff1a;时针旋转台的HarmonyOS实现这个项目实现了一个基于HarmonyOS的交互式时钟应用&#xff0c;通过可视化方式展示时针旋转角度与时间分类的关系。不同于传统时钟应用&#xff0c;它特别突出了角度计算与时间显示的关联性&#xff0c;可以作为教学演示工具或…

作者头像 李华
网站建设 2026/9/14 16:24:28

MMC分布式储能系统双模式控制与优化实践

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/14 16:23:18

2020款iMac:Intel时代最后一台专业级桌面工作站

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/14 16:22:59

PTA天梯赛L3-027:Java与C++的算法复杂度优化实战

1. 题目背景与竞赛解析 PTA团体程序设计天梯赛是国内高校程序设计领域的重量级赛事&#xff0c;由全国高等学校计算机教育研究会主办。比赛分为"珠峰争鼎"、"华山论剑"、"沧海竞舟"三个组别&#xff0c;分别对应不同难度层级。L3级别的题目属于竞…

作者头像 李华
网站建设 2026/9/14 16:18:39

从Forrester TEI报告看腾讯云TKE:容器化的经济账怎么算?

第一次完整读完2024年Forrester这份《腾讯云容器服务总体经济影响™报告》的时候&#xff0c;我脑子里冒出来的第一个念头不是“省了多少钱”&#xff0c;而是“终于有人把这笔账算明白了”。接触容器和Kubernetes这些年&#xff0c;我见过太多团队卡在立项阶段&#xff1a;技术…

作者头像 李华
网站建设 2026/9/14 16:18:28

Qt图像处理核心:QImage像素操作与格式转换实战

1. 项目概述&#xff1a;为什么像素级操作是Qt图像处理的分水岭在Qt图像处理的实际项目里&#xff0c;绝大多数人卡在“能显示图片”和“能调用OpenCV滤镜”这两个阶段。但真正决定你能不能做工业检测、医疗影像预处理、嵌入式视觉前端、甚至实时UI特效的&#xff0c;从来不是Q…

作者头像 李华