Skip to content
This repository has been archived by the owner on Oct 19, 2024. It is now read-only.

Book - Providers Chapter #2023

Merged
merged 13 commits into from
Jan 17, 2023
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions book/SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,15 @@
- [Connect to an Ethereum node](./getting-started/connect_to_an_ethereum_node.md)

# Reference guide
- [Providers]()
- [Providers](./providers/providers.md)
- [Http](./providers/http.md)
- [WebSocket](./providers/ws.md)
- [IPC](./providers/ipc.md)
- [Mock](./providers/mock.md)
- [Quorum](./providers/quorum.md)
- [Retry](./providers/retry.md)
- [RW](./providers/rw.md)
- [WebSocket](./providers/ws.md)
- [Advanced Usage](./providers/advanced_usage.md)
- [Middleware]()
- [Builder]()
- [Create custom middleware]()
Expand Down
5 changes: 5 additions & 0 deletions book/providers/advanced_usage.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Advanced Usage


### `CallBuilder`
Todo: Mention how to use [state override set](https://geth.ethereum.org/docs/interacting-with-geth/rpc/ns-eth#3-object---state-override-set)
142 changes: 139 additions & 3 deletions book/providers/http.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,141 @@
# Http provider
# Http

The `Http` provider establishes an Http connection with a node, allowing you to send RPC requests to the node to fetch data, simulate calls, send transactions and much more.

## Initializing an Http Provider
Lets take a quick look at few ways to create a new `Http` provider. Since the `Http` provider implements the [`FromStr`](https://doc.rust-lang.org/std/str/trait.FromStr.html) one of the easiest ways to initialize a new provider is by using the `from_str()` method.

```rust
use ethers::providers::{Http, Middleware, Provider};

#[tokio::main]
async fn main() -> eyre::Result<()> {
//Initialize a new Http provider
let rpc_url = "https://mainnet.infura.io/v3/c60b0bb42f8a4c6481ecd229eddaca27";
let provider: Provider<Http> = Provider::<Http>::from_str(rpc_url)?;

Ok(())
}
```

The `Http` provider also supplies a way to initialize a new authorized connection.

```rust
// Initializes a new HTTP Client with authentication
use ethers::providers::{Authorization, Http};
use url::Url;
#[tokio::main]
async fn main() -> eyre::Result<()> {
let url = Url::parse("http://localhost:8545").unwrap();
let provider = Http::new_with_auth(url, Authorization::basic("admin", "good_password"));
Ok(())
}
```

Additionally, you can initialize a new provider with your own custom `reqwest::Client`.

```rust
use ethers::providers::Http;
use url::Url;

#[tokio::main]
async fn main() -> eyre::Result<()> {
let url = Url::parse("http://localhost:8545").unwrap();
let client = reqwest::Client::builder().build().unwrap();
let provider = Http::new_with_client(url, client);
Ok(())
}
```

## Basic Usage

Now that you have successfully established an Http connection with the node, you can use any of the methods provided by the `Middleware` trait. In the code snippet below, the provider is used to get the chain id, current block number and the content of the node's mempool.

```rust
use ethers::providers::{Http, Middleware, Provider};

#[tokio::main]
async fn main() -> eyre::Result<()> {
let rpc_url = "https://mainnet.infura.io/v3/c60b0bb42f8a4c6481ecd229eddaca27";
let provider: Provider<Http> = Provider::<Http>::from_str(rpc_url)?;

let chain_id = provider.get_chainid().await?;
let block_number = provider.get_block_number().await?;
let tx_pool_content = provider.txpool_content().await?;

Ok(())
}
```

You can also use the provider to interact with smart contracts. The snippet below uses the provider to establish a new instance of a UniswapV2Pool and uses the `get_reserves()` method from the smart contract to fetch the current state of the pool's reserves.

```rust
use std::{str::FromStr, sync::Arc};

use ethers::{
prelude::abigen,
providers::{Http, Provider},
types::H160,
};

abigen!(
IUniswapV2Pair,
r#"[function getReserves() external view returns (uint112 reserve0, uint112 reserve1, uint32 blockTimestampLast)]"#
);

#[tokio::main]
async fn main() -> eyre::Result<()> {
let rpc_url = "https://mainnet.infura.io/v3/c60b0bb42f8a4c6481ecd229eddaca27";
let provider: Arc<Provider<Http>> = Arc::new(Provider::<Http>::try_from(rpc_url).unwrap());

//Initialize a new instance of the Weth/Dai Uniswap V2 pair contract
let pair_address = H160::from_str("0xA478c2975Ab1Ea89e8196811F51A7B7Ade33eB11").unwrap();
let uniswap_v2_pair = IUniswapV2Pair::new(pair_address, provider);

//Use the get_reserves() function to fetch the pool reserves
let (reserve_0, reserve_1, block_timestamp_last) =
uniswap_v2_pair.get_reserves().call().await?;

Ok(())
}
```

This example is a little more complicated, so lets walk through what is going on. The `IUniswapV2Pair` is a struct that is generated from the `abigen!()` macro. The `IUniswapV2Pair::new()` function is used to create a new instance of the contract, taking in an `Address` and an `Arc<M>` as arguments, where `M` is any type that implements the `Middleware` trait. Note that the provider is wrapped in an [`Arc`](https://doc.rust-lang.org/std/sync/struct.Arc.html) when being passed into the `new()` function.

It is very common to wrap a provider in an `Arc` to share the provider across threads. Lets look at another example where the provider is used asynchronously across two tokio threads. In the next example, a new provider is initialized and used to asynchronously fetch the number of Ommer blocks from the most recent block, as well as the previous block.

```rust
{{#include ../../examples/providers/examples/http.rs}}
```
use std::sync::Arc;

use ethers::providers::{Http, Middleware, Provider};

#[tokio::main]
async fn main() -> eyre::Result<()> {
let rpc_url = "https://mainnet.infura.io/v3/c60b0bb42f8a4c6481ecd229eddaca27";
let provider: Arc<Provider<Http>> = Arc::new(Provider::<Http>::try_from(rpc_url).unwrap());
0xKitsune marked this conversation as resolved.
Show resolved Hide resolved

let current_block_number = provider.get_block_number().await?;
let prev_block_number = current_block_number - 1;

//Clone the Arc<Provider> and pass it into a new thread to get the uncle count of the current block
0xKitsune marked this conversation as resolved.
Show resolved Hide resolved
let provider_1 = provider.clone();
let task_0 =
tokio::spawn(async move { provider_1.get_uncle_count(current_block_number).await });

//Spin up a new thread to get the uncle count of the previous block
let task_1 = tokio::spawn(async move { provider.get_uncle_count(prev_block_number).await });

//Wait for the tasks to finish
for task in [task_0, task_1] {
if let Ok(uncle_count) = task.await? {
println!("Success!");
}
}

Ok(())
}
```

<br>

Before heading to the next chapter, feel free to check out the docs for the [`Http` provider](https://docs.rs/ethers/latest/ethers/providers/struct.Http.html). Keep in mind that we will cover advanced usage of providers at the end of this chapter. Now that we have the basics covered, lets move on to the next Provider, Websockets!
81 changes: 79 additions & 2 deletions book/providers/ipc.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,82 @@
# IPC provider
The IPC (Inter-Process Communication) transport is a way for a process to communicate with a running Ethereum client over a local Unix domain socket. If you are new to IPC, you can [follow this link to learn more](). Using the IPC transport allows the ethers library to send JSON-RPC requests to the Ethereum client and receive responses, without the need for a network connection or HTTP server. This can be useful for interacting with a local Ethereum node that is running on the same machine. Using Ipc [is faster than RPC](), however you will need to have a local node that you can connect to.

## Initializing an Ipc Provider

```rust
{{#include ../../examples/providers/examples/ipc.rs}}
```
#[tokio::main]
async fn main() -> eyre::Result<()> {

// We instantiate the provider using the path of a local Unix domain socket
// --------------------------------------------------------------------------------
// NOTE: The IPC transport supports push notifications, but we still need to specify a polling
// interval because only subscribe RPC calls (e.g., transactions, blocks, events) support push
// notifications in Ethereum's RPC API. For other calls we must use repeated polling for many
// operations even with the IPC transport.
let provider = Provider::connect_ipc("~/.ethereum/geth.ipc")
.await?
.interval(std::time::Duration::from_millis(2000));

Ok(())
}
```
## Usage

The `Ipc` provider has the same methods as the `Ws` provider, allowing it to subscribe and unsubscribe via a `NotificationStream`.


```rust
use ethers::providers::{Middleware, Provider, StreamExt, Ws};

#[tokio::main]
async fn main() -> eyre::Result<()> {
let provider = Provider::connect_ipc("~/.ethereum/geth.ipc")
.await?
.interval(std::time::Duration::from_millis(2000));
0xKitsune marked this conversation as resolved.
Show resolved Hide resolved

//Create a new stream yielding pending transactions from the mempool
let mut tx_pool_stream = provider.subscribe_pending_txs().await?;

while let Some(tx_hash) = tx_pool_stream.next().await {
println!("Pending tx: {:?}", tx_hash);
}

Ok(())
}
```


Note that the `Ipc` provider, like all providers, has access to the methods defined by the `Middleware` trait. With this in mind, we can use the `Ipc` provider just like the `Http` provider as well, with the only difference being that we are connected to the node via a Unix socket now!


```rust
use std::{str::FromStr, sync::Arc};

use ethers::{
prelude::abigen,
providers::{Http, Provider},
types::H160,
};

abigen!(
IUniswapV2Pair,
r#"[function getReserves() external view returns (uint112 reserve0, uint112 reserve1, uint32 blockTimestampLast)]"#
);

#[tokio::main]
async fn main() -> eyre::Result<()> {
let provider = Provider::connect_ipc("~/.ethereum/geth.ipc")
.await?
.interval(std::time::Duration::from_millis(2000));

//Initialize a new instance of the Weth/Dai Uniswap V2 pair contract
let pair_address = H160::from_str("0xA478c2975Ab1Ea89e8196811F51A7B7Ade33eB11").unwrap();
let uniswap_v2_pair = IUniswapV2Pair::new(pair_address, provider);

//Use the get_reserves() function to fetch the pool reserves
let (reserve_0, reserve_1, block_timestamp_last) =
uniswap_v2_pair.get_reserves().call().await?;

Ok(())
}
```
30 changes: 30 additions & 0 deletions book/providers/providers.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# Providers

Providers play a central role in `ethers-rs`, enabling you to establish asynchronous [Ethereum JSON-RPC](https://github.com/ethereum/wiki/wiki/JSON-RPC) compliant clients.

your program to a node to get data, interact with smart contracts, listen to the mempool and much more. There are a few different types of default providers that are built into the library. The default providers are `Http`,`WS`,`Ipc`,`RWClient`,`Quorum`,`Mock` and `RetryClient`. In addition to all of these options, you can also create your own custom provider, which we will walk through later in this chapter. For now let take a look at what the `Provider` actually looks like.
gakonst marked this conversation as resolved.
Show resolved Hide resolved


```rust
#[derive(Clone, Debug)]
pub struct Provider<P> {
inner: P,
ens: Option<Address>,
interval: Option<Duration>,
from: Option<Address>,
/// Node client hasn't been checked yet= `None`
/// Unsupported node client = `Some(None)`
/// Supported node client = `Some(Some(NodeClient))`
_node_client: Arc<Mutex<Option<NodeClient>>>,
}
```


<!--TODO: Need to fill out a quick description for the ens and _node_client fields -->
The `Provider` struct defines a generic type `P` that can be any type that implements the [`JsonRpcClient` trait](https://docs.rs/ethers/latest/ethers/providers/trait.JsonRpcClient.html). The `inner` field stores the type that implements the `JsonRpcClient` type, allowing the Provider to make RPC calls to the node. The `ens` field is an optional value that xyz. The `interval` field is an optional value that defines the polling interval when for streams (subscribing to logs, block headers, etc.). The `from` field is an optional type that allows you to set a default "from" address when constructing transactions and making calls. Lastly, the `_node_client` field is xyz.


Note that all providers implement the [`Middleware` trait](https://docs.rs/ethers/latest/ethers/providers/trait.Middleware.html), which gives every provider access to [commonly used methods](https://docs.rs/ethers/latest/ethers/providers/struct.Provider.html#impl-Middleware-for-Provider%3CP%3E) to interact with the node. Later in this chapter, we will go over these methods and examples for how to use them in detail. Additionally, `Middleware` will be covered extensively in a later chapter.

Now that you have a basis for what the `Provider` type actually is, the next few sections will walk through each implementation of the `Provider`, starting with the HTTP provider.

71 changes: 70 additions & 1 deletion book/providers/ws.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,74 @@
# WebSocket provider
The Ws provider allows you to send JSON-RPC requests and receive responses over WebSocket connections. The WS provider can be used with any Ethereum node that supports WebSocket connections. This allows programs interact with the network in real-time without the need for HTTP polling for things like new block headers and filter logs. Ethers-rs has support for WebSockets via Tokio. Make sure that you have the “ws” and “rustls” / “openssl” features enabled in your project's toml file if you wish to use WebSockets.



## Initializing a WS Provider
Lets look at a few ways to create a new `WS` provider.


```rust
#[tokio::main]
async fn main() -> eyre::Result<()> {
let ws_endpoint = "";
let provider = Provider::<Ws>::connect(ws_endpoint).await?;
Ok(())
}
```

TODO: note on setting the polling interval


TODO: note on initializing a new ws provider with authorization like the http provider
```rust
{{#include ../../examples/providers/examples/ws.rs}}
#[tokio::main]
async fn main() -> eyre::Result<()> {
let ws_endpoint = "";
let auth = Authorization::basic("username", "password");

if let Ok(_provider) = Provider::<Ws>::connect_with_auth(url, auth).await {
println!("Create Ws provider with auth");
}

Ok(())
}
```



TODO: snippet of creating a new ws provider with a custom WS. Initializes a new WebSocket Client, given a Stream/Sink Websocket implementer. The websocket connection must be initiated separately.

```rust
#[tokio::main]
async fn main() -> eyre::Result<()> {
let ws_endpoint = "";
let auth = Authorization::basic("username", "password");

if let Ok(_provider) = Provider::<Ws>::connect_with_auth(url, auth).await {
println!("Create Ws provider with auth");
}

Ok(())
}
```

## Usage
TODO: Examples of syncing to new blocks, filter logs. Mention that the WS client implements the `PubSubClient` trait which gives access to the subscribe and unsubscribe methods.

```rust
use ethers::providers::{Middleware, Provider, StreamExt, Ws};

#[tokio::main]
async fn main() -> eyre::Result<()> {
let ws_endpoint = "";
let provider = Provider::<Ws>::connect(ws_endpoint).await?;
//Create a new stream yielding pending transactions from the mempool
let mut tx_pool_stream = provider.subscribe_pending_txs().await?;

while let Some(tx_hash) = tx_pool_stream.next().await {
println!("Pending tx: {:?}", tx_hash);
}

Ok(())
}
```