ENS的学习记录

写在前面

最近在学习Ethereum,学习完基础语法之后,就想学习点项目。机缘巧合下,遇到了ENS项目。由于英语底子较差,想找篇中文文档看看ENS主要是做什么的时候,发现并没有找到合适的。硬着头皮看了10多天,终于把ENS的官方文档看了七七八八,现在把学习的过程记录下来。希望能够对想学习的人能够有所帮助。

ENS综述

什么是ENS?

ENS是以太坊名称服务,一种基于以太坊区块链的分布式,开放和可扩展的命名系统。

ENS (Ethereum Name Service) 的功能类似我们较熟悉的DNS(Domain Name Service)网域名称服务,但提供的不是Internet网址,而是将以太坊(Ethereum)钱包地址和智能合约地址以xxxxxxx.eth网址的方式表示,可以用于转帐或存取智能合约等地方。

顶级域名,如“.eth”和“.test”(.eth是根服务器),由名为注册商的智能合约所有(根服务器),后者指定管理子域名分配的规则。任何人都可以按照这些注册商合同规定的规则获得二级域名的所有权供自己使用。

上面的说法来自于ENS的官方文档,下面是我自己的想法:
我的理解:ENS可以理解为以太坊为我们提供的免费的域名服务,我们只需要缴纳押金和执行智能合约的gas,等到不想用的时候,ENS会把钱退给我们。

快速了解ENS

ENS两个基本组件

上图是ENS的两个基本组件:分别是注册表和解析器。
其中ENS注册表由智能合约组成,维护着所有的域名和子域名,存储着以下三种信息:

  • 域名的所有者
  • 域名的解析者
  • 在该域名下,所有记录的生存时间

域名的所有者或许是外部账户或许是一个智能合约。注册服务商是在ENS中拥有名称并且根据规则分配子域的智能合约。

注册服务商可以:

  • 设置域名的解析者和TTL
  • 把一个域名的所有权转移到另外的地址
  • 改变子域名的所有权

解析器由用户实现,负责把名称映射为地址。任何实现相关ENS标准的合约都可以充当解析者。

以太坊上的ENS

ENS是一个智能合约,被部署在主网上, 0x314159265dd8dbb310642f98f50c066173c1259b,可以在此注册。

ENS还部署在测试网络上,测试网络有两个顶级域名,.eth(和主网相同的功能,允许用户长期持有)和.test(用来测试,有效期28天)。

使用向导

可以使用javaScript控制台和web3.js来注册,配置和更新ENS名字。在正式操作之前,需要下载ensutils.js或者ensutils-testnet.js,然后导入到正在运行的以太坊控制台中。
先把本机的网络与以太网进行同步:进入控制台geth console,然后进行同步。eth.syncing。同步完成后,在打开另外一个终端,输入geth --testnet attach。进行启动以太坊控制台。
让人抓狂的是,必须要等到本地和以太坊的测试网络全部同步完成,这是一个非常非常漫长的过程。
接来下导入ensutils-testnet.js文件。loadScript('./ensutils-testnet.js'); 同样需要注意,路径一定要写正确。

注意:

这里写图片描述
在使用geth attach时,我遇到了上面的错误,最初我看到connection refused的时候,以为是权限问题,使用chmod和sudo之后,还是不能连接。经过google搜索之后,在项目的issues里面,有人给出了解决的办法。首先贴出解决的办法:
1.geth --testnet --cache 2048 --rpc
2.geth attach ipc:/Users/anapodoton/Library/Ethereum/testnet/geth.ipc
需要注意的是anapodoton是我的电脑用户名。
接下来我对attach命令进行了比较深入的学习。通过官网的介绍,我学习到console命令用于启动一个geth节点,然后打开控制台。attach用于在一个正在运行的节点上打开控制台。
通过官网的描述,我们知道attach命令需要在一个正在运行的节点上启动。所以我们需要为attach命令指定一个具体的ipc文件。我们第一步我们设置了缓存大小,启动了HTTP-RPC服务器。紧接着我们指定了ipc文件的具体路径。通过上面的方法,问题就可以得到解决了。此外,官网上还有其他的一些解决方法,由于我没有尝试,就不在一一叙述。

补充:什么是ipc?

ipc是进程间通信的缩写,在安装的时候不会产生。在以太坊中,仅仅当运行节点的时候才会产生。在必要的时候我们需要进行明确的指定,我们也可以把ipc文件的路径设置为默认值。geth --ipcpath "/path/to/my/geth.ipc"
好了,到这一步,终于把上个问题的错误给搞定了。

使用FIFS注册服务商注册一个名字

部署在Ropsten上的ENS合约使用先来先服务的机制为.test顶级域名提供注册服务。该域名的过期时间是28天。

值得注意的是,官方文档上的testRegistrar只进行了声明,没有实例化,我们需要先进行实例化。步骤如下:
1.personal.newAccount("hjs")
2.var hjs ="0x2b3e03589fe8670e5cb18fc1b0702884021c62f3"
3.var myRegister = fifsRegistrarContract.at(hjs)
通过上面三步,即可使用。

ensutils.js定义了一个对象testRegistrar,此外,可以自己定义一个实例用于和合约进行交互。var myRegistrar = fifsRegistrarContract.at(address);
在注册前,先检查下是否已经有人拥有该名字new Date(testRegistrar.expiryTimes(web3.sha3('myname')).toNumber() * 1000) 如果返回的时间值比当前时间早(在此之前,该名字已过期),说明可以使用该名字。

可以使用下面的方法注册一个名字,第一个参数是名字的hash值,第二个参数是拥有该名字的地址。testRegistrar.register(web3.sha3('myname'), eth.accounts[0], {from: eth.accounts[0]}); 交易执行成功后,名字即可进行分配。

通过竞拍的方式注册一个名字

主网上的顶级域名.eth通过拍卖的方式被处理,测试网络上可以立即得到。
注册服务商采用盲投的方式,名字最少是7个字符。
用户注册一个域名需要以下几个步骤:

开始拍卖

在出价之前,需要运行下面的代码检查域名是否可用。ethRegistrar.entries(web3.sha3('name'))[0];
执行结果会返回0-5六个整数中的一个,分别代表不同的状态:

  • 0:域名可用,拍卖未开始;
  • 1:名字可用,正在被拍卖,此时用户可以直接参与竞标;
  • 2:该域名目前已经被人拥有;
  • 3:该域名被禁止使用;
  • 4:该域名当前已经在拍卖的揭示阶段;
  • 5:由于该域名目前在“软启动阶段”,所以不可使用

如果返回值是5,可以使用下面的代码检查该域名什么时候可以开始拍卖:new Date(ethRegistrar.getAllowedTime(web3.sha3('name')) * 1000);
可以使用startAution方法开始拍卖:

ethRegistrar.startAuction(web3.sha3('name'), {from:eth.accounts[0], gas: 100000});
也可以同时拍卖多个名字,来伪装真正感兴趣的名字:ethRegistrar.startAuctions([web3.sha3('decoy1'), web3.sha3('name'), web3.sha('decoy2')], {from: eth.accounts[0], gas: 1000000});
一个拍卖过程通常需要持续5提案时间,3天的竞价时间和2天的揭示时间。在最初部署的时候,会有一个“软启动阶段”,在此期间,域名将会逐渐的发布以进行竞拍。软启动阶段在主网中持续13周,测试网络中持续4周的时间。
如果一个 域名处在拍卖期,可以使用下面的方法检查拍卖的接受时间:new Date(ethRegistrar.entries(web3.sha3('name'))[2].toNumber() * 1000)

出价

除了最后48个小时外,其他拍卖的时间都可以出价。(因为最后48个小时是揭示期),只有一次出价机会。
为了保证公开拍卖的公平,需要使用下面的数据:

  • 需要注册的名字;
  • 地址;
  • 愿意支付的最大金额;
  • 一个随机数(盐);

除此之外,必须提供愿意提供多少存款在以太坊中(需要大于出价),目的是为了掩盖出价的真实值。
首先使用随机数产生器生成一个加密的值。这个值需要保存好,因为在揭示阶段需要使用。
下面可以使用下面的代码生成"sealed"出价:var bid = ethRegistrar.shaBid(web3.sha3('name'), eth.accounts[0], web3.toWei(1, 'ether'), web3.sha3('secret'));解释下参数的含义,第一个参数是注册的域名,第二个参数是地址,第三个参数是最大出价,第四个参数是前面产生的加密值。报价账户是域名的所有者,为了安全考虑,不要把这些信息发送给其他账户。
接下来,可以把出价信息发送给注册服务管理商:

ethRegistrar.newBid(bid, {from: eth.accounts[0], value: web3.toWei(2, 'ether'), gas: 500000});

在该例子中,我们虽然出价是1个以太币,但是我们发送了两个以太币,目的是掩盖我们的真实出价,在揭示阶段,多余的出价会退还给我们。
现在等待直到揭示阶段即可:new Date(ethRegistrar.entries(web3.sha3('name'))[2].toNumber() * 1000)

揭示出价

在最后的48个小时,一定记得揭示,在此期间,不允许新的出价。如果未揭示出价,之前的出价无效,且抵押的钱不会退还,所以要保存好之前的盐。
可以使用下面的方法,进行揭示:ethRegistrar.unsealBid(web3.sha3('name'), web3.toWei(1, 'ether'), web3.sha3('secret'), {from: eth.accounts[0], gas: 500000});
unsealBid和shaBid的参数列表相同,除了不必提供注册的地址。
出价揭示之后,拍卖的状态将会被更新。
如果你的出价比已经揭示的出价低,将会收到退款;
如果你的出价目前是最高的,那么暂时只会退还一部分(实际发送的金额-出价金额),剩余的部分的金额仍会被锁定。

检查竞拍的结果

在揭示阶段的任何时候,都可以使用下面的方法检查当前的拍卖获胜者:deedContract.at(ethRegistrar.entries(web3.sha3('name'))[1]).owner();
也可以查看当前最高出价的金额:web3.fromWei(ethRegistrar.entries(web3.sha3('name'))[4], 'ether');

拍卖结束

一旦拍卖结束,域名将会分配给获胜者(如果最高出价人有多人,则按出价时间排序,最早出价的人获得该域名)。仅仅获胜者可以调用下面的方法:ethRegistrar.finalizeAuction(web3.sha3('name'), {from: eth.accounts[0], gas: 500000}); 该方法一旦被调用,获胜者将会为第二高出价的人退款。如果仅仅有一个人在竞争,那么可以得到0.01eth以上部分的退款。获胜者将会获得ENS域名的所有权。

管理所有权

在域名持有期内,用户可以将域名绑定到自己的以太坊地址 、转移域名 的使用权 、添加设置子域名等,甚至还可以转让域名的所有权 。如ens.setOwnerethRegistrar.finalizeAuctionethRegistrar.transfer(web3.sha3('name'), newDeedOwnerAddress, {from: currentDeedOwnerAddress})
注意:转让域名的所有权是不可撤回的,必须三思而后行。除此之外,之前缴纳的押金,也一并会转移到新的所有者。

与ENS注册表进行交互

ENS注册表是ENS的核心组件,把名称和解析器进行一一映射,还包括所有者的名字以及他们的TTL(缓存生存时间)。
需要首先获取一个ENS域名,才可以和ENS注册表进行交互。
还可以从其他拥有域名的人那里获取一个子域名,值得注意的是,尽管任何人都可以部署ENS服务,但是必须要使用他们的代码。

获得一个域名的所有者

可以使用下面的方法可以获取一个域名的所有者 ens.owner(namehash('somename.eth'));

获得一个域名的解析者

> ens.resolver(namehash('somename.eth'));

设置一个域名的解析者

使用下面的方法设置域名的解析者
> ens.setResolver(namehash('somename.eth'), resolverAddress, {from: eth.accounts[0]});
一个解析者是实现了解析接口(在EIP137中规定的)的任何合约,可以自己去部署,也可以使用公共的。在ensutils.js中提供了publicResolver方法。使用方法,首先设置域名和解析者的地址:ens.setResolver(namehash('somename.eth'), publicResolver.address, {from: eth.accounts[0]}); ,然后使用下面的方法把地址和域名进行绑定:

publicResolver.setAddr(namehash('somename.eth'), eth.accounts[0], {from: eth.accounts[0]})

转移域名

可以使用下面的方法把域名进行转移:> ens.setOwner(namehash('somename.eth'), newOwner, {from: eth.accounts[0]});

创建子域名

可以使用下面的方法创建子域名。> ens.setSubnodeOwner(namehash('somename.eth'), web3.sha3('foo'), eth.accounts[0], {from: eth.accounts[0]});
域名的所有者可以在任何时候分配子域名的所有者,即使该域名已经归属于某人。

实现向导

下面是这样一个教程:如何使用ENS实现一个工具和应用?如何在ENS中自定义解析器?

只需要3步即可在应用中添加ENS

在应用程序中可以集成ENS,有多个重要的功能,可以单独实现,下面分别介绍一下三个功能:

在应用程序中任何可以接收地址的地方接收ENS域名

为了在应用程序中使用ENS,必须把ENS接入。
这样的话,只需要用户输入名字即可,而不是你的地址,这样,当你的地址发生变化的时候,用户感觉不到任何变化。
如果您的程序是用来处理资金相关的,您可能希望追踪地址的变化并且提供给用户。

在应用程序需要展示地址的地方换成ENS名字

如果用户之前输入了域名,那么以后只给用户展示域名而不是地址。
如果用户输入地址,尽量展示名字。

允许用户为他们的资源注册名字

有两种方式:

  1. 提供给用户简单的方式去注册一个ENS名字和他们的资源相关;
  2. 提供给用户简单的方式去更新他们资源的名字;

ENS库文件

ENS提供了非常多的库文件,方便我们的使用。

解析域名

在ENS中解析域名分为以下三步:

  1. 把名字进行hash
  2. 查询ENS注册表的地址以获得该名称对应的解析程序的地址。
  3. 查询解析器获得想查取资源的地址。
    这里写图片描述
    首先调用resolver(bytes32)方法,并且传递namehash参数查询注册表。注册表将会返回负责解析该名称的地址。如果未配置解析程序或者改名称不存在,则返回0。
    如果注册表的返回值非零,则可以查询需要解析的地址。例如,需要查询与某个名称相关地址,则使用解析器的addr(bytes32)方法,再次传入namehash,即可得到该资源对应的实际的地址。

反向解析

写一个解析器

解析器在EIP137中被明确指定,解析器必须实现下面的方法:

function supportsInterface(bytes4 interfaceID) constant returns (bool)

supportsInterface在EIP165中定义,允许调用者确定该解析器是否支持特定的记录类型。记录类型被指定为解析器必须一起实现的一个或者多个方法。目前支持的记录类型如下所示:
这里写图片描述
supportsInterface的返回值必须是interfaceID的值0x01ffc9ac,也是supportsInterface自身的ID。
例如,一个简单的解析器,仅仅支持addr类型看起来像下面这样:
contract SimpleResolver { function supportsInterface(bytes4 interfaceID) constant returns (bool) { return interfaceID == 0x3b3b57de; } function addr(bytes32 nodeID) constant returns (address) { return address(this); } }
不重要的合约返回当前的地址。正式的解析器可以使用任何机制返回希望得到的地址。

在链上解析域名

Solidity在链上的解析至今还不能用,但是ENS是可以用的,可以使用以下的接口:
`contract ENS {
function owner(bytes32 node) constant returns (address);
function resolver(bytes32 node) constant returns (Resolver);
function ttl(bytes32 node) constant returns (uint64);
function setOwner(bytes32 node, address owner);
function setSubnodeOwner(bytes32 node, bytes32 label, address owner);
function setResolver(bytes32 node, address resolver);
function setTTL(bytes32 node, uint64 ttl);
}

contract Resolver {
function addr(bytes32 node) constant returns (address);
}`

解析器仅仅需要实现resolver()即可,以下是个例子:

contract MyContract { ENS ens; function MyContract(address ensAddress) { ens = ENS(ensAddress); } function resolve(bytes32 node) constant returns(address) { var resolver = ens.resolver(node) return resolver.addr(node); } }

实现一个注册商

ENS中的注册商只是一个拥有名称的合约,并且能够根据合约代码定义的规则进行对子域的分配。下面是一个例子:
contract FIFSRegistrar { ENS ens; bytes32 rootNode; function FIFSRegistrar(address ensAddr, bytes32 node) { ens = ENS(ensAddr); rootNode = node; } function register(bytes32 subnode, address owner) { var node = sha3(rootNode, subnode); var currentOwner = ens.owner(node); if (currentOwner != 0 && currentOwner != msg.sender) throw; ens.setSubnodeOwner(rootNode, subnode, owner); } }
可以为新用户设置自定义的规则。

更新ENS记录

为用户提供更新名字的功能(和解析域名是相似的):

  1. 把需要解析的名字进行hash。
  2. 查询ENS注册表的地址以获得该名称对应的解析程序的地址。
  3. 在解析程序上调用合适的更新方法。

第一步和第二步和解析域名一样,如果第二步失败,需要进行修改。
第三步调用解析器中定义的方法。例如:设置和名字相关联的地址。

NameHash

ENS中的名称被表示为32个字节的hash,而不是纯文本格式,这简化了处理和存储,同时允许任意长度的域名,保护额隐私。用于将域名转换为hash的算法叫namehash,在EIP137中定义。
为了保留名称的层次性,namehash是递归定义的,可以从父域名和子域的标签得到子域名。

术语

  • 域名:人类可读的,完整的名字,例如"vitalik.wallet.eth"。
  • 标签:域名的单个组件,如vitalik,wallet,eth等。不包括小数点(.)。
  • 标签的hash:keccak-256函数的输出,例如:keccak256(‘eth’) = 0x4f5b812789fc606be1b3b16908db13fc7a9adf7ca72641f84d75b47069d3d7f0
  • 节点:namehash函数的输出

算法

域名被用小数点来划分,例如‘vitalik.wallet.eth’ 变成 [‘vitalik’, ‘wallet’, ‘eth’]。
namehash函数采用递归进行定义:namehash([]) = 0x0000000000000000000000000000000000000000000000000000000000000000 namehash([label, …]) = keccak256(namehash(…), keccak256(label))
用python实现如下:

def namehash(name):
  if name == '':
    return '' * 32
  else:
    label, _, remainder = name.partition('.')
    return sha3(namehash(remainder) + sha3(label))

名字的规范化和验证

在名字转换为节点hash之前,必须对名称的规范性进行检查,比如禁止一些特殊的字符。

处理含义不清的名字

由于Unicode中存在着大量的字符,并且所表示的脚本种类繁多。因此可能产生同性恋攻击。应该对用户做出提示。

ENS中主要合约的简述

ENSRegistry.sol

实现了ENS注册表的功能,是用于查找解析器和域名所有者的核心合约。

FIFSRegistrar.sol

实现了先来先服务的注册机制,为第一个请求的子域名进行分发。

HashRegistrar.sol

采用盲拍进行竞价,续约过程根据注册域名的平均价格的变化进行加权。

HashRegistrarSimplified.sol

上面的简化版本,不支持续约。

PublicResolver.sol

简易的解析器的实现,允许任何域名的所有者进行配置。

结束语

由于时间比较紧迫,此篇文章写得更多的像是对官方文档的翻译,在加上我接触以太坊的时间并不算长,所以有些地方上的理解会有些偏差,希望大家多多指教。好了,暂时就是这些了,以后想到了再来补充。

原文地址:https://www.cnblogs.com/hjs-junyu/p/9431947.html