Rustでweb3

# 概要

Ethereumにアクセスするインターフェースと言えばJavaScripの[web3.js](https://github.com/ethereum/web3.js)やPythonの[Web3.py](https://github.com/ethereum/web3.py)などがよく使われていると思います。
RustでもEthereumをさわりたいなということで、[rust-web3](https://github.com/tomusdrw/rust-web3)を使ってみます。

# 環境等

* Rust 1.28.0
* MacOSX 10.13.6
* rust-web3 0.3.1

# 小さな例

このあたりはREADMEとかの通りなのですがメモ

## HTTP
プロバイダにHTTPを使う場合は以下のようになります。

```rust
extern crate web3;

use web3::futures::Future;

fn main() {
    let (_eloop, transport) = web3::transports::Http::new("http://localhost:8545").unwrap();
    let web3obj = web3::Web3::new(transport);
    let accounts = web3obj.eth().accounts().wait().unwrap();
    println!("accounts: {:?}", accounts);
}
```

## IPC

IPCプロバイダも同じように使えます。

```rust
extern crate web3;

use web3::futures::Future;

fn main() {
    let (eloop, transport) = web3::transports::Ipc::new("/path/to/rpc.ipc").unwrap();
    let web3obj = web3::Web3::new(transport);
    let accounts = web3obj.eth().accounts().wait().unwrap();
    println!("accounts: {:?}", accounts);
}
```

## WebSocket
プロバイダにWebSocketを使う場合は以下のようになります。

```rust
extern crate web3;

use web3::futures::Future;

fn main() {
    let (_eloop, ws) = web3::transports::WebSocket::new("ws://localhost:8546").unwrap();
    let web3 = web3::Web3::new(ws);
    let accounts = web3.eth().accounts().wait().unwrap();

    println!("accounts: {:?}", accounts);
}
```

が、これを実行するとアカウントの情報を表示したままハングします。
切断明示的にしないとダメっぽいけどどうすりゃいいのか。（また調べてみます。）

ということで、WebSocket使いたい場合はtokioと組み合わせて使う必要があります。

```rust
extern crate tokio_core;
extern crate web3;

use web3::futures::Future;

fn main() {
    let mut event_loop = tokio_core::reactor::Core::new().unwrap();

    let web3 = web3::Web3::new(
        web3::transports::WebSocket::with_event_loop(
            "ws://localhost:8546",
            &event_loop.handle(),
        ).unwrap(),
    );
    let accounts = web3.eth().accounts().map(|accounts| {
        println!("accounts: {:?}", accounts);
    });

    event_loop.run(accounts).unwrap();
}
```

これで切断されて実行終了します。

もちろんHTTP、IPCプロバイダもtokioと組み合わせて実行することが可能です。

# ENSの情報を取得する

[web3.py](https://web3py.readthedocs.io/en/stable/index.html)ではユーティリティとして[ENS API](https://web3py.readthedocs.io/en/stable/ens.html)が提供されていますが、JavaScript版やRust版のweb3では使えません。
そこで、rust-web3を使った例として以下ENSにアクセスする方法を紹介してみます。

## ENS

そもそも[ENS](https://ens.domains/)とはなんぞやというところなのですが、DNSライクな感じで、 *hogehoge.eth* という名前から *0x126bf276ba4c7111dbddbb542718cff678c9b3cd* のようなEthereumのアドレスを得たり、その逆ができたりします。

事前に.eth名の取得、取得した名前とアドレスとの紐付け等を行っておく必要がありますが、ここでは省略します。

## 実装

ではさっそくコード例とともに説明していきます。

### namehash

まずENSで欠かせない処理といえばnamehashです。
.eth名やアドレスは全てnamehash化してからweb3インターフェースに渡します。
以下のようなコードです。

```rust
extern crate tiny_keccak;

use tiny_keccak::Keccak;

fn namehash(name: &str) -> Vec<u8> {
    let mut node = vec![0u8; 32];
    if name.is_empty() {
        return node;
    }
    let mut labels: Vec<&str> = name.split(".").collect();
    labels.reverse();
    for label in labels.iter() {
        let mut labelhash = [0u8; 32];
        Keccak::keccak256(label.as_bytes(), &mut labelhash);
        node.append(&mut labelhash.to_vec());
        labelhash = [0u8; 32];
        Keccak::keccak256(node.as_slice(), &mut labelhash);
        node = labelhash.to_vec();
    }
    node
}
```

### ENS Contract / PublicResolver Contract

[ENS用のコントラクトはこちらで公開されており](https://github.com/ensdomains/ens)、これを使ってアクセスします。所有者情報やENSリゾルバにアクセスできる `ENS.sol` と、ENSリゾルバ用の `PublicResolver.sol` を使います。

[ensdomains/ens: Implementations for registrars and local resolvers for the Ethereum Name Service](https://github.com/ensdomains/ens)

* ENS.sol
* PublicResolver.sol

rust-web3でコントラクトを扱う時は.abiファイルを読み込む必要があるので、事前に`.sol`から`.abi`ファイルを生成しておきます。

```shell-session
$ mkdir contract
$ solc --bin --abi --optimize contract/ENS.sol -o contract
$ solc --bin --abi --optimize contract/PublicResolver.sol -o contract
$ ls
ENS.abi             ENS.bin             ENS.sol
PublicResolver.abi  PublicResolver.bin  PublicResolver.sol
```

### コントラクトのアドレス

ENS.solはパブリックなコントラクトがすでにデプロイされているので、コントラクトアドレスはそれを使います。

```rust
const ENS_MAINNET_CONTRACT_ADDR: &str = "314159265dD8dbb310642f98f50C066173C1259b";
```

これをrust-web3で使う際には `web3::types::Address` (`web3::types::H160` のエイリアス)で扱う必要があるので、以下のようなコードで変換して使います。

```rust
fn main() {
    let ens_contract_addr: Address = ENS_MAINNET_CONTRACT_ADDR.parse().unwrap();
        :
}
```

### 所有者情報を取得

HTTPアクセスするweb3インターフェース作って、コントラクトを読み出して、`owner`メソッドを呼んで所有者情報を取得まで。

```rust
fn main() {
    let ens_contract_addr: Address = ENS_MAINNET_CONTRACT_ADDR.parse().unwrap();
    let (_evloop, transport) = web3::transports::Http::new("http://localhost:8545").unwrap();

    let ens_name = "hexacosa.eth";
    let web3obj = web3::Web3::new(transport);
    let contract = Contract::from_json(
        web3obj.eth(), ens_contract_addr, include_bytes!("../contract/ENS.abi"),
    ).unwrap();

    // namehash化したアドレスを渡す
    let ens_namehash = H256::from_slice(namehash(ens_name).as_slice());

    // ownerメソッドを呼び出し
    let result = contract.query("owner", (ens_namehash, ), None, Options::default(), None);
    let owner: Address = result.wait().expect("owner.result.wait()");
    println!("ens name: {:?}, namehash: {:?}", ens_name, ens_namehash);
    println!("owner: {:?}", owner);
}
```

### ENSリゾルバを使って.eth名からアドレスを取得

ENSコントラクトからENSリゾルバを取得して、そのリゾルバ経由で.eth名からEthereumアドレスを取得します。

```rust
fn main() {
    // ens_namehashを取得するところまでは同じ
        :
    // .eth名をインプットにしてresolverメソッドでENSリゾルバのアドレスを取得
    let result = contract.query("resolver", (ens_namehash, ), None, Options::default(), None);
    let resolver_addr: Address = result.wait().unwrap();

    // ENSリゾルバアドレスを指定してリゾルバ用のコントラクトを取得
    let resolver_contract = Contract::from_json(
        web3obj.eth(), resolver_addr, include_bytes!("../contract/PublicResolver.abi"),
    ).unwrap();

    // .eth名をインプットにしてaddrメソッドを呼び出し、アドレスを得る
    let ens_namehash = H256::from_slice(namehash(ens_name).as_slice());
    let result = resolver_contract.query("addr", (ens_namehash, ), None, Options::default(), None);
    let resolve_address: Address = result.wait().expect("addr.result.wait()");
    println!("ens name: {:?}, namehash: {:?}", ens_name, ens_namehash);
    println!("address: {:?}", resolve_address);
}
```

### ENSリゾルバを使ってアドレスから.eth名を取得
こちらは `0x...` から `.eth` への変換です。（ここでは逆引きと呼びます）

逆引きには逆引き用のENSリゾルバを取得する必要があります。
ENSリゾルバを取得するコードは以下のようなになります。

```rust
const ENS_REVERSE_REGISTRAR_DOMAIN: &str = "addr.reverse";

fn main() {
        :
    // 逆引き対象のアドレス
    let address: Address = "006f7da5fe8b1a45d364a677499333099cdb4b75".parse().unwrap();

    // "{アドレス}.addr.reverse"をnamehash化
    let reverse_resolver_name = format!("{:x}.{}", address, ENS_REVERSE_REGISTRAR_DOMAIN);
    let addr_namehash = H256::from_slice(namehash(reverse_resolver_name.as_str()).as_slice());

    // 上記のnamehash化したアドレスを渡してENS逆引き用のリゾルバを得る
    let result = contract.query("resolver", (addr_namehash, ), None, Options::default(), None);
    let resolver_addr: Address = result.wait().unwrap();
        :
}
```

あとはENS逆引き用のコントラクトを取得し、nameメソッドを呼び出すことで.eth名を得ることができます。

```rust
fn main() {
        :
    let resolver_contract = Contract::from_json(
        web3obj.eth(), resolver_addr, include_bytes!("../contract/PublicResolver.abi"),
    ).unwrap();
    let result = resolver_contract.query("name", (addr_namehash, ), None, Options::default(), None);
    let resolve_name: String = result.wait().unwrap();
    println!("ens name: {:?}", resolve_name);
}
```


# rust-ens

と、ここまでrust-web3でENSを利用する方法を説明してきましたが、毎回書きたくないよねということで、このあたりの処理をライブラリとしてまとめたものを[rust-ens](https://github.com/hhatto/rust-ens)として公開していますのでよければ使ってみてください。

[crates.io](https://crates.io/crates/ens)にも登録済みなので、以下のような形で使うことができます。

```toml:Cargo.toml
web3 = "0.3.1"
ens = "0.1.0"
```

```rust:src/main.rs
extern crate ens;
extern crate web3;

use ens::ENS;

fn main() {
    let (_evloop, transport) = web3::transports::Http::new(
        "http://localhost:8545",
    ).unwrap();

    let ens_name = "hexacosa.eth";

    let ens = ENS::new(web3::Web3::new(transport));
    match ens.address(ens_name) {
        Ok(addr) => println!("{:?}", addr),
        Err(_) => println!("unknown"),
    };

    let address: web3::types::Address = "006f7da5fe8b1a45d364a677499333099cdb4b75".parse().unwrap();
    match ens.name(address) {
        Ok(addr) => println!("{:?}", addr),
        Err(_) => println!("unknown"),
    };
}
```