用Rust来做以太坊开发1之客户端
本系列文章主要是用Rust
的ethers-rs
来复刻《用Go来做以太坊开发》这本书本的内容,所以本系列文章的标题叫做《用Rust来做以太坊开发》, 算是ethers-rs
的快速入门教程, 因为原书写得足够好了,所以本系列更多的只是代码层面的复刻,不会说明太多相关的基础知识。
本书和《用Go来做以太坊开发》这本书秉承一样的宗旨
本意是如果你已经对以太坊和Rust有一些熟悉,但是对于怎么把两者结合起来还有些无从下手,那这本书就是一个好的起点 - 改自《用Go来做以太坊开发》
关于Ethereum的开发可以一般来说分为两类。
- 链上开发,也就是开发合约,这部分一般使用solidity编程语言来开发。
- 链外开发,也就是跟区块链的交互,比如读取区块数据,发送交易及与合约交互等。
其实还有一种,那就是侧链或者说layer2之类的开发。
本系列文章主要专注于第二种。
本文用到的依赖如下:
ethers = {version="2.0", features=["rusttls", "ws"]}
tokio = {version="1", features=["full"]}
eyre = "0.6"
快速入门
ethers-rs
和go-ethereum
的概念有点不一样,前者叫provider, 后者叫client,provider这个概念应该是从ethers.js
哪里继承过来的,如果你使用过ethers.js
那么对于ethers-rs
的很多概念应该比较熟悉。
use ethers::prelude::*;
const RPC_URL: &str = "https://cloudflare-eth.com";
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let provider = Provider::<Http>::try_from(RPC_URL)?;
let block_number = provider.get_block_number().await?;
println!("当前节点区块高度: {block_number}");
Ok(())
}
客户端/provider
要想跟以太坊网络交互,自然是要先连接网络中的一个节点的,对于节点我们有很多选项,大致分为两类,公开(外部)的和私有的,下面是一些公开的免费接口(都需要注册), 私有的自然是需要自己搭建,这里就不赘述了。
- Etherscan
- INFURA
- Alchemy
- Pocket Gateway
- Ankr
具体的优缺点和详细对比参考:https://docs.ethers.org/v5/api-keys/
如果查询的数据比较简单,比如只是区块链的基本数据(区块hash, 交易列表,事件列表)那么可以直接使用免费公开的接口如:
- https://cloudflare-eth.com
- wss://cloudflare-eth.com
上面的不同主要在于使用的应用协议不同,如果要做监听类的操作,我们需要使用wss(Websocket)协议。
当我们确定好要连接的节点之后,就可以使用代码连接了。
use ethers::prelude::*;
const HTTP_RPC_URL: &str = "https://cloudflare-eth.com";
const WEBSOCKET_RPC_URL: &str = "wss://cloudflare-eth.com";
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let provider = Provider::<Http>::try_from(HTTP_RPC_URL)?;
let block_number = provider.get_block_number().await?;
println!("current block number: {block_number}");
let provider = Provider::<Ws>::connect(WEBSOCKET_RPC_URL).await?;
let block_number = provider.get_block_number().await?;
println!("current block number: {block_number}");
Ok(())
}
对于本地连接,一种更高效的连接方式是IPC连接。
use ethers::providers::Provider;
const IPC_RPC_URL: &str = "~/.ethereum/geth.ipc";
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let provider = Provider::connect_ipc(IPC_RPC_URL)?;
let block_number = provider.get_block_number().await?;
println!("current block number: {block_number}");
Ok(())
}
至此我们有了一个可以访问以太坊网络的客户端,但是网络不总是可靠的,比如网络掉线或者网络重连等,所以我们不得不处理这些琐碎的问题,这些问题一般处理起来比较烦人,不过庆幸的是,ethers-rs
已经为我们做了这些琐碎的工作,它提供了很多额外的包装,比如Quorum
,Retry
,RW
等包装类型,它们分别代表的功能如下
- Quorum: 在创建的时候设置多个请求后端,只有当结果超过50%的值一样才会返回,比如创建的时候设置了三个节点,分别返回了[1,1,3], 那么Quorum这个类型的客户端会返回1,即使结果3最先返回。
- Retry: 这个应该很好理解,就是重试,我们可以设置最大重试次数和回退的初始值。
- RW: 这个就是读写分离,一个用来读,一个用来写,这个一定程度上也能提升性能。
具体示例代码参考官方文档吧:
- https://gakonst.com/ethers-rs/providers/quorum.html
- https://gakonst.com/ethers-rs/providers/retry.html
- https://gakonst.com/ethers-rs/providers/rw.html
除此之外,ethers-rs
还提供Mock
用于测试, 以及自定义的扩展。
小结
本文只是简单的罗列了ethers-rs
的各种构建客户端的方式,对应着《用Go来做以太坊开发》的第一章, 和go-ethereum
不同的是,ethers-rs
提供了很多额外的包装类型,可以不用处理重试,数据一致性(一定程度上的数据一致性, 这对Quorum选择的多个节点有要求)等一些常见的问题,通过这些包装类型我们可以写出更安全更高效的代码。