Aptos Rust REST 客户端实战:aptos-rest-client 示例程序运行与源码解析
【免费下载链接】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-rest-client是 aptos-core 仓库中封装 Aptos Full Node REST API 的 Rust SDK 客户端。本仓库在 examples 目录 中提供了一组可执行示例(目前为account示例),官方文档将其定位为“端到端测试”:它们不接入标准cargo test流程,而是要求开发者本地(或远程)已有一个运行中的 Aptos API 服务,用真实请求验证客户端各条查询链路。读完本篇,你可以直接在本地跑通该示例,理解--api-url参数的解析方式、示例依次调用的每个 REST 端点,以及底层Client/ClientBuilder/State/RestError的关键实现细节。
一、示例的定位:面向真实 API 的端到端验证
crates/aptos-rest-client/examples/README.md 对示例的定位有两点关键说明:
- 这些示例实质上是REST 客户端的端到端测试("Really these examples serve as end-to-end tests for the REST client");
- 它们不是标准测试,不应该、也不会随标准
cargo test一起运行,因为执行它们的前提是:已经有一个 Aptos API 在运行。
从 示例入口 可以看到参数定义:
#[derive(Debug, Parser)] #[clap(author, version, about)] pub struct Args { /// This should include the port, e.g. http://127.0.0.1:8080 #[clap(long)] api_url: Url, }api_url是一个url::Url类型字段,注释明确要求必须带上端口。这意味着 URL 必须是合法的绝对地址(如http://127.0.0.1:8080),传入不合法字符串时 clap 会在解析阶段直接报错退出,而不是运行到一半才发现地址错误。
二、运行方式:一条命令打满一条查询链路
从aptos-rest-client包的父目录(即 crates/aptos-rest-client)执行:
cargo run --example <dir>文档给出的完整示例是:
cargo run --example account -- --api-url http://127.0.0.1:8080其中:
account对应examples/account/main.rs,是目录名即 example 名;--api-url后面必须是指向 Full Node REST 服务的地址,默认本地 Full Node 的 REST 端口为8080。
运行成功后,示例会以info!日志逐条打印查询结果,形如:
Running all queries against account: 0x0000000000000000000000000000000000000000000000000000000000000001 Successfully retrieved N account resources with JSON Successfully retrieved N account resources with BCS Successfully retrieved N account modules with JSON Successfully retrieved N account modules with BCS Successfully retrieved resource 0x1::chain_id::ChainId with JSON Successfully retrieved balance ... Successfully retrieved resource 0x1::chain_id::ChainId with BCS示例入口在 main.rs L22-L31 中初始化日志、解析参数并构造客户端:
let args = Args::parse(); let client = Client::new(args.api_url); let address = AccountAddress::ONE; info!("Running all queries against account: {}", address);所有查询统一打在系统账户0x1(AccountAddress::ONE,即框架核心代码地址)上,该账户天然持有ChainId资源,保证查询不会落空。
三、account 示例逐条调用链拆解
按 main.rs L33-L100 的执行顺序,示例覆盖了两类端点形态:JSON 与 BCS。
3.1 账户资源与模块的整页查询(JSON + BCS 双形态)
let results = client.get_account_resources(address).await?; // JSON let results = client.get_account_resources_bcs(address).await?; // BCS let results = client.get_account_modules(address).await?; // JSON let results = client.get_account_modules_bcs(address).await?; // BCS这四个方法对应GET /v1/accounts/{address}/resources与GET /v1/accounts/{address}/modules端点。注意它们的实现并非单次请求,而是自动游标分页:在 src/lib.rs 中,get_account_resources内部调用paginate_with_cursor,每页请求量由常量控制:
const RESOURCES_PER_CALL_PAGINATION: u64 = 9999; const MODULES_PER_CALL_PAGINATION: u64 = 1000;(见 lib.rs L64-L65。)分页循环会持续携带服务端返回的cursor请求下一页,直到响应头中不再带游标为止(paginate_with_cursor 实现)。因此results.inner().len()打印的是跨页合并后的总数,这也是示例日志中数字可能远超单页上限的原因。
3.2 单个资源查询:0x1::chain_id::ChainId
let resource = "0x1::chain_id::ChainId"; client.get_account_resource(address, resource).await?; // JSON,返回 Option<Resource> client.get_account_resource_bcs::<ChainId>(address, resource).await?; // BCS,直接反序列化为 ChainId 类型这里体现了一条重要设计:BCS 泛型方法get_account_resource_bcs<T: DeserializeOwned>直接把 BCS 字节解码为目标 Rust 类型(lib.rs L1212-L1224),而 JSON 版本返回的是Option<Resource>,由调用方决定是否解析data字段。示例同时验证两条路径,确保两种序列化格式都能正确返回ChainId资源。
3.3 新旧两套余额查询接口的交叉校验
示例中最有“端到端测试”味道的一段是 main.rs L77-L94:
let balance_from_new_api = client .get_account_balance(address, "0x1::aptos_coin::AptosCoin") .await?; let balance_from_old_api = client.view_apt_account_balance(address).await?; assert_eq!(balance_from_new_api.inner(), balance_from_old_api.inner());get_account_balance走的是GET /v1/accounts/{address}/balance/{asset_type}端点,asset_type可以是 coin 类型或 fungible asset 元数据地址(见 lib.rs L318-L335 的方法注释);view_apt_account_balance走的是 view function 端点POST /v1/view,内部执行0x1::coin::balance(view_account_balance_bcs_impl)。
两条路径语义等价,示例用assert_eq!断言二者结果一致——任何一侧端点行为回归都会直接让程序 panic 失败。
3.4 一个容易被忽略的细节:示例自带单元测试
main.rs L105-L109 在main之外还定义了一个真正的单元测试:
#[test] fn verify_tool() { use clap::CommandFactory; Args::command().debug_assert() }它用 clap 的debug_assert校验参数定义(如--api-url长选项与字段名一致、帮助文本无冲突等)。也就是说,虽然示例本身不在标准测试里跑,但这条 clap 参数自检仍然会随cargo test -p aptos-rest-client一起执行,保证命令行接口不会在重构中悄悄损坏。
四、客户端底层实现要点(源码佐证)
示例里一行Client::new(args.api_url)背后,ClientBuilder做了不少默认配置,理解它们对排查真实环境问题很有帮助。
4.1 默认配置:版本路径、超时、请求头
client_builder.rs L34-L109 中:
- 版本路径前缀:默认为
v1/(DEFAULT_VERSION_PATH_BASE,见 lib.rs L58)。构造 URL 时所有端点路径都会先拼接该前缀,例如accounts/0x1/resources最终变成{base}/v1/accounts/0x1/resources。若自定义base_url已带路径(如https://host/v2),get_version_path_with_base会优先沿用 base URL 自带的路径(lib.rs L1927-L1941); - 超时:默认 10 秒(
timeout: Duration::from_secs(10),client_builder.rs L54),可通过ClientBuilder::timeout()覆盖; - 客户端标识头:自动注入
X-Aptos-Client请求头,取值为aptos-rust-sdk/{crate 版本号}(client_builder.rs L44-L48),服务端可据此统计 SDK 流量; - API Key 环境变量:若环境中设置了
X_API_KEY,会自动以Authorization: Bearer {key}形式附加到请求头(client_builder.rs L58-L60)。对接需要密钥的托管 API 网关时无需改代码; - Cookie 存储开启(
cookie_store(true)),以适配需要会话态的 API 网关场景。
4.2 每次响应的 State 元数据:从 HTTP 头解析
所有成功的响应都会被包装成Response<T>,其中附带一个State结构(state.rs L10-L21),包含chain_id、epoch、ledger version、timestamp、block_height、分页cursor、encryption_key等字段。这些值全部来自服务端响应头中的X-Aptos-Chain-Id、X-Aptos-Ledger-Version、X-Aptos-Cursor等自定义头(State::from_headers)。
这一点有两层实际意义:
- 一致性校验:
get_ledger_information内部会断言头解析出的 chain_id/epoch/version 与响应体字段一致(lib.rs L397-L417); - 分页闭环:
paginate_with_cursor依赖的游标就是从X-Aptos-Cursor头里取的(cursor.clone_from(&response.state().cursor))。如果服务端少返回这些头,State::from_headers会直接报错而非静默继续——这是排查“分页卡死/数据缺失”时应先检查的点。
4.3 错误模型与可重试判断
客户端错误统一收敛到RestError枚举(error.rs L146-L162),区分 API 错误(Api,带状态码与链上状态)、BCS/JSON 反序列化错误、URL 解析错误、HTTP 层错误等。对于需要重试的场景,lib.rs L1943-L1957 提供retriable判定:仅429 Too Many Requests、500、502、503、504、507 Insufficient Storage视为可重试,另有retriable_with_404变体把 404 也纳入可重试范围。配套的try_until_ok辅助函数(lib.rs L1784-L1834)以指数退避(初始 1s,翻倍,默认总等待 60s)执行重试。示例程序未直接使用重试逻辑,但生产代码里对全节点 API 的轮询(如wait_for_transaction_by_hash)本质上依赖同样的状态机思路:轮询间隔 500ms,并带“服务端滞后容忍窗口”(默认 60s,DEFAULT_MAX_SERVER_LAG_WAIT_DURATION)以避免因 Full Node 同步延迟而误判交易丢失。
五、适用前提与注意事项
- 必须先有运行中的 Aptos API:本地 Full Node 默认 REST 端口为
8080,也可指向 Devnet/Testnet 的公共 API 地址。ClientBuilder同时内置了AptosBaseUrl::Mainnet/Devnet/Testnet三个预设地址(client_builder.rs L16-L32),示例使用的是Custom分支,即你自己传入的 URL; - 示例不是单元测试:不要在 CI 里把
cargo run --example account当作无依赖测试运行,它需要外部服务,失败与否取决于 API 端状态; - 依赖版本:
aptos-rest-client依赖aptos-api-types、aptos-types、reqwest等 workspace 依赖(见 Cargo.toml L19-L34),作为库引入其他工程时注意这些传递依赖; - 日志输出:示例通过
aptos_logger::Logger::new().init()初始化日志,debug!/info!输出会按日志级别落到终端,排查连接问题时可调高日志级别观察重试与轮询细节。
六、延伸阅读路径
| 内容 | 路径 |
|---|---|
| 示例运行说明(本文主体文档) | crates/aptos-rest-client/examples/README.md |
| account 示例入口 | crates/aptos-rest-client/examples/account/main.rs |
| 客户端核心实现(Client、分页、重试) | crates/aptos-rest-client/src/lib.rs |
| 构建器与默认配置 | crates/aptos-rest-client/src/client_builder.rs |
| State 头解析 | crates/aptos-rest-client/src/state.rs |
| 错误类型 | crates/aptos-rest-client/src/error.rs |
| 包定义与依赖 | crates/aptos-rest-client/Cargo.toml |
综上,examples/account虽然只有百余行代码,但恰好串起了aptos-rest-client最常用的能力面:双序列化格式(JSON/BCS)的资源与模块查询、单资源精确读取、新旧余额接口的等价性校验,以及 clap 参数自检。把它当作“可运行的接口契约”来跑一遍,再对照上文的ClientBuilder默认值、State头解析与RestError模型阅读源码,是理解这个 Rust SDK 最快的路径。
【免费下载链接】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),仅供参考