用Rust来做以太坊开发1之客户端

文章目录

本系列文章主要是用Rustethers-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-rsgo-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

要想跟以太坊网络交互,自然是要先连接网络中的一个节点的,对于节点我们有很多选项,大致分为两类,公开(外部)的和私有的,下面是一些公开的免费接口(都需要注册), 私有的自然是需要自己搭建,这里就不赘述了。

  1. Etherscan
  2. INFURA
  3. Alchemy
  4. Pocket Gateway
  5. Ankr

具体的优缺点和详细对比参考:https://docs.ethers.org/v5/api-keys/

如果查询的数据比较简单,比如只是区块链的基本数据(区块hash, 交易列表,事件列表)那么可以直接使用免费公开的接口如:

上面的不同主要在于使用的应用协议不同,如果要做监听类的操作,我们需要使用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已经为我们做了这些琐碎的工作,它提供了很多额外的包装,比如QuorumRetryRW等包装类型,它们分别代表的功能如下

  • Quorum: 在创建的时候设置多个请求后端,只有当结果超过50%的值一样才会返回,比如创建的时候设置了三个节点,分别返回了[1,1,3], 那么Quorum这个类型的客户端会返回1,即使结果3最先返回。
  • Retry: 这个应该很好理解,就是重试,我们可以设置最大重试次数和回退的初始值。
  • RW: 这个就是读写分离,一个用来读,一个用来写,这个一定程度上也能提升性能。

具体示例代码参考官方文档吧:

除此之外,ethers-rs还提供Mock用于测试, 以及自定义的扩展。

小结

本文只是简单的罗列了ethers-rs的各种构建客户端的方式,对应着《用Go来做以太坊开发》的第一章, 和go-ethereum不同的是,ethers-rs提供了很多额外的包装类型,可以不用处理重试,数据一致性(一定程度上的数据一致性, 这对Quorum选择的多个节点有要求)等一些常见的问题,通过这些包装类型我们可以写出更安全更高效的代码。

参考链接