LoginSignup
11
8

More than 5 years have passed since last update.

Rustでweb3

Posted at

概要

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

環境等

  • Rust 1.28.0
  • MacOSX 10.13.6
  • rust-web3 0.3.1

小さな例

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

HTTP

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

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プロバイダも同じように使えます。

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を使う場合は以下のようになります。

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と組み合わせて使う必要があります。

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ではユーティリティとしてENS APIが提供されていますが、JavaScript版やRust版のweb3では使えません。
そこで、rust-web3を使った例として以下ENSにアクセスする方法を紹介してみます。

ENS

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

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

実装

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

namehash

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

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用のコントラクトはこちらで公開されており、これを使ってアクセスします。所有者情報やENSリゾルバにアクセスできる ENS.sol と、ENSリゾルバ用の PublicResolver.sol を使います。

ensdomains/ens: Implementations for registrars and local resolvers for the Ethereum Name Service

  • ENS.sol
  • PublicResolver.sol

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

$ 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はパブリックなコントラクトがすでにデプロイされているので、コントラクトアドレスはそれを使います。

const ENS_MAINNET_CONTRACT_ADDR: &str = "314159265dD8dbb310642f98f50C066173C1259b";

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

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

所有者情報を取得

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

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アドレスを取得します。

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リゾルバを取得するコードは以下のようなになります。

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名を得ることができます。

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として公開していますのでよければ使ってみてください。

crates.ioにも登録済みなので、以下のような形で使うことができます。

Cargo.toml
web3 = "0.3.1"
ens = "0.1.0"
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"),
    };
}
11
8
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
11
8