概要
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にも登録済みなので、以下のような形で使うことができます。
web3 = "0.3.1"
ens = "0.1.0"
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"),
};
}