研究のために、Ganache-CLIを使って、研究室にプライベートなブロックチェーンを組んでいる。
自分の研究はスマートコントラクトをゴリゴリ書くようなものなので、色々なアドレスを記憶しておく必要がありぶっちゃけちょっと面倒くさい。
ENS(Ethereum Name Service)という、名前解決(名前からアドレスを返す)仕組みが存在するのは知っていたので簡単に出来るかな〜と思ったら思ったより理解が必要だったので、自分と同じくらいのモチベーションで名前解決を試してみる人の負担の軽減を目的としてこの記事を書いてみる。
この記事では以下のことを目標にする。
- 自分の立てたプライベートチェーン上でENSRegistryをデプロイしたりしつつ、ドメインの割り当て方法について知る
- ethers.js で名前解決できるようにする。
自分(か知っている人)以外はドメインの割り当てを行わないという仮定の元、正しい手順をスキップしてドメインを割り当てていく。
結局は名前解決がしたいだけなので。
ピリオド区切りの名前解決についてはよく分からんかったので今回は無し。 “hoge” みたいな一単語についての名前解決を試みる。
ENSを構成するコントラクト
今回やりたいことを達成するためには、二つだけコントラクトを知っていれば良い。
ENSRegistry
ens/ENSRegistry.sol at master · ensdomains/ens
メインの処理を行うコントラクト。レコードの設定や、名前解決がしたいときはこのコントラクトを通して行う。
PublicResolver
ens-contracts/PublicResolver.sol at master · ensdomains/ens-contracts
実際に名前解決を行うコントラクト。名前 => アドレスのマッピングはこのコントラクトが持っている。
名前解決の仕組み
名前: “nyaa” に対応するアドレスを取得する。
つまり、新たにドメイン"hoge"の名前解決を可能にしたい場合は二つの操作が必要となる。
- ENSRegistryへ
resolver("hoge")
を送ったら"hoge"のマッピングを持っているPublicResolverを返す様にする
- “hoge"のマッピングを持っているPublicResolverに対して、
addr("hoge")
と送ったら"hoge"のアドレスを返す様にする
実装
今回はPublicResolverは一つだけデプロイすれば良い。つまり、どのようなアドレスを送っても同じPublicResolverを返すようにする。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
|
import {INameWrapper, PublicResolver} from '@ensdomains/ens-contracts/contracts/resolvers/PublicResolver.sol';
import '@ensdomains/ens-contracts/contracts/registry/ENSRegistry.sol';
import {ReverseRegistrar} from '@ensdomains/ens-contracts/contracts/registry/ReverseRegistrar.sol';
contract ENSDeployer {
bytes32 public constant TLD_LABEL = keccak256('reth');
bytes32 public constant RESOLVER_LABEL = keccak256('resolver');
ENSRegistry public ens;
PublicResolver public publicResolver;
function namehash(bytes32 node, bytes32 label) public pure returns (bytes32) {
return keccak256(abi.encodePacked(node,label));
}
constructor() public {
ens = new ENSRegistry();
publicResolver = new PublicResolver(ens,INameWrapper(address(0)),address(0),address(0));
bytes32 resolverNode = namehash(bytes32(0), RESOLVER_LABEL);
// resolver setup
ens.setSubnodeOwner(bytes32(0), RESOLVER_LABEL, address(this));
ens.setResolver(resolverNode, address(publicResolver));
publicResolver.setAddr(resolverNode, address(publicResolver));
}
function setDomain(string memory domain,address a) public {
bytes32 label = keccak256(bytes(domain));
bytes32 node = namehash(bytes32(0), label);
ens.setSubnodeOwner(bytes32(0),label,address(this));
ens.setResolver(node, address(publicResolver));
publicResolver.setAddr(node, a);
}
// デバッグ用
function fetchResolver(string memory domain) public view returns (address) {
bytes32 label = keccak256(bytes(domain));
bytes32 node = namehash(bytes32(0), label);
return ens.resolver(node);
}
}
|
ポイントとしては、
- 親ノードがいないとnodeは新しく設定できないので、ENSDeployerのコンストラクタで設定されていた、ゼロアドレスを親ノードとしてサブノードで新しくpublicResolverへのマッピングを作る
これ本当に書いといてくれや・・・・
リゾルバ側はsetAddrで、簡単に対応関係が設定できる。
Tips
- ethers.jsでensをJsonRpcなプロパイダで有効にするときは第二引数を使って設定できる。
1
2
3
4
5
6
7
|
const fetchProvider = () => {
const network = {
...,
ensAddress: ensRegistryAddress,
}
return new ethers.providers.JsonRpcProvider("http://LOCAL_IP",network);
}
|
1
|
await provider.resolveName('hoge'); // => 'hoge' の address
|
うまくいかないとき
大体、requireがコケてる。つまり、権限が足りてない。今回はENSDeployerっていうコントラクトを作って、ENSRegistryやPublicResolverのデプロイをしつつ、ドメインのセットはその中でやっているから、オーナー権限で操作できて問題は無かったが、これをethersとかからやるとなるとまた別の苦労が必要になると思う。 各コントラクトの権限チェッカである、 isAuthorised(bytes32 node)
を見てそれを満たしているかどうか確認していく必要がある。
参考
自分でプライベートチェーンにENSをデプロイするにあたってはこの辺りのページが参考になった。
おまけ 書いたPlantUML置き場
1
2
3
4
5
6
7
8
9
10
11
12
13
|
@startuml
Actor User
participant ENSRegistry
participant PublicResolver
User -> ENSRegistry: resolver("nyaa")
ENSRegistry -> User: "nyaa" のアドレスが格納されているPublicResolverのアドレス
User -> PublicResolver: addr("nyaa")
PublicResolver -> User: "nyaa" に対応するアドレス
@enduml
|