Day 8
AENS 域名服务
高级阶段 · 预计学习时间 120 分钟
学习目标
- 理解 AENS 域名系统
- 掌握域名注册完整流程
- 熟悉域名状态和生命周期
- 实现域名指针配置
- 完成域名转让和撤销
AENS 概述
什么是 AENS
AENS(Aeternity Naming System)是 Aeternity 区块链的域名系统,将人类可读的名称映射到区块链地址,类似于互联网的 DNS 系统。
示例:
alice.chain → ak_2gx9MEFxKvY9vMG5YnqnXWv1hCsX7rgnfvBLJS4aQurustR1rt
mytoken.chain → ct_2AfnEfCSZCTEkxL5Yoi4Yfq6fF7YapHRaFKDJK3THMXMBspp5z
域名命名规则
| 规则 | 说明 |
|---|---|
| 后缀 | 必须以 .chain 结尾 |
| 长度 | 最少 1 个字符(不含后缀) |
| 字符 | 小写字母、数字、连字符 |
| 限制 | 不能以连字符开头或结尾 |
✅ 有效示例
alice.chainmy-wallet.chaintoken123.chain
❌ 无效示例
Alice.chain (大写)my_wallet.chain (下划线)-start.chain (连字符开头)
域名状态与生命周期
域名状态
| 状态 | 说明 | 可执行操作 |
|---|---|---|
| AVAILABLE | 可用,未被注册 | preclaim |
| PRECLAIMED | 已预声明 | claim |
| CLAIMED | 已注册 | update, transfer, revoke |
| REVOKED | 已撤销 | preclaim(重新注册) |
注册流程
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ AVAILABLE │────→│ PRECLAIMED │────→│ CLAIMED │
└─────────────┘ └─────────────┘ └─────────────┘
↑ preclaim claim
│
│ ┌─────────────┐
└──│ REVOKED │
└─────────────┘
revoke
为什么需要两步注册?
防止抢注攻击。Preclaim 提交承诺(commitment),不暴露域名;Claim 揭示域名,完成注册。
费用计算
| 域名长度 | 费用(约) | 说明 |
|---|---|---|
| 1 字符 | ~570 AE | 极高 |
| 2 字符 | ~352 AE | 很高 |
| 3 字符 | ~218 AE | 高 |
| 4 字符 | ~135 AE | 中等 |
| 5+ 字符 | ~83 AE | 基础 |
实践任务
任务 8.1: 检查域名可用性
from aeternity.node import NodeClient, Config
from aeternity import aens
# 连接测试网
config = Config(external_url='https://testnet.aeternity.io')
client = NodeClient(config)
print(f"当前区块高度: {client.get_current_key_block_height()}")
# 检查域名
domains_to_check = [
"test-domain-12345.chain",
"aeternity.chain",
"my-unique-name.chain"
]
for domain in domains_to_check:
print(f"\n检查域名: {domain}")
try:
name_info = client.get_name_entry_by_name(domain)
print(f" 状态: 已注册")
print(f" 所有者: {name_info.owner}")
print(f" TTL: 区块 {name_info.ttl}")
if name_info.pointers:
print(f" 指针:")
for pointer in name_info.pointers:
print(f" {pointer.key}: {pointer.id}")
except Exception as e:
if "not found" in str(e).lower() or "404" in str(e):
print(f" 状态: 可用(未注册)")
else:
print(f" 查询失败: {e}")
任务 8.2: 域名注册 - Preclaim
from aeternity.signing import Account
import secrets
# 创建或加载账户(需要有测试币)
account = Account.generate()
print(f"账户地址: {account.get_address()}")
# 生成唯一域名
random_suffix = secrets.token_hex(4)
domain_name = f"mytest-{random_suffix}.chain"
print(f"\n准备注册域名: {domain_name}")
# 生成 commitment(承诺)
salt = aens.generate_commitment_salt()
commitment = aens.compute_commitment(domain_name, salt)
print(f"\nPreclaim 信息:")
print(f" 域名: {domain_name}")
print(f" Salt: {salt.hex()}")
print(f" Commitment: {commitment}")
# 发送 Preclaim 交易
try:
preclaim_tx = client.name_preclaim(
account=account,
commitment_id=commitment
)
print(f"\nPreclaim 交易:")
print(f" 交易哈希: {preclaim_tx.hash}")
print(f" 状态: 成功")
print(f"\n⚠️ 保存以下信息用于 Claim:")
print(f" 域名: {domain_name}")
print(f" Salt: {salt.hex()}")
except Exception as e:
print(f"\nPreclaim 失败: {e}")
任务 8.3: 域名注册 - Claim
import time
# Preclaim 时保存的信息
domain_name = "mytest-xxxxx.chain" # 替换为实际域名
salt = bytes.fromhex("xxx...") # 替换为实际 salt
print(f"准备 Claim 域名: {domain_name}")
# 等待 Preclaim 确认(至少 1 个区块)
print("\n等待 Preclaim 确认...")
time.sleep(5) # 测试网约 3 秒一个区块
# 发送 Claim 交易
try:
claim_tx = client.name_claim(
account=account,
name=domain_name,
name_salt=salt
)
print(f"\nClaim 交易:")
print(f" 交易哈希: {claim_tx.hash}")
print(f" 域名 ID: {claim_tx.name_id}")
print(f" 状态: 成功")
# 查询注册结果
name_info = client.get_name_entry_by_name(domain_name)
print(f"\n域名信息:")
print(f" 所有者: {name_info.owner}")
print(f" TTL: 区块 {name_info.ttl}")
except Exception as e:
print(f"\nClaim 失败: {e}")
任务 8.4: 完整注册流程(一站式)
class AENSManager:
"""AENS 域名管理器"""
def __init__(self, client: NodeClient, account: Account):
self.client = client
self.account = account
def is_available(self, name: str) -> bool:
"""检查域名是否可用"""
try:
self.client.get_name_entry_by_name(name)
return False
except:
return True
def register(self, name: str, wait_blocks: int = 2) -> dict:
"""注册域名(完整流程)"""
print(f"开始注册域名: {name}")
if not self.is_available(name):
raise ValueError(f"域名 {name} 已被注册")
print(" ✓ 域名可用")
# Step 1: Preclaim
salt = aens.generate_commitment_salt()
commitment = aens.compute_commitment(name, salt)
preclaim_tx = self.client.name_preclaim(
account=self.account,
commitment_id=commitment
)
print(f" ✓ Preclaim 完成: {preclaim_tx.hash}")
# 等待确认
print(f" ⏳ 等待 {wait_blocks} 个区块确认...")
initial_height = self.client.get_current_key_block_height()
while True:
current_height = self.client.get_current_key_block_height()
if current_height >= initial_height + wait_blocks:
break
time.sleep(2)
# Step 2: Claim
claim_tx = self.client.name_claim(
account=self.account,
name=name,
name_salt=salt
)
print(f" ✓ Claim 完成: {claim_tx.hash}")
name_info = self.client.get_name_entry_by_name(name)
return {
"name": name,
"name_id": claim_tx.name_id,
"owner": name_info.owner,
"ttl": name_info.ttl
}
# 使用示例
manager = AENSManager(client, account)
result = manager.register(f"demo-{secrets.token_hex(4)}.chain")
print(f"\n🎉 域名注册成功!")
print(f" 域名: {result['name']}")
任务 8.5: 更新域名指针
# 准备指针
pointers = [
{"key": "account_pubkey", "id": account.get_address()},
# 也可以指向合约
# {"key": "contract_pubkey", "id": "ct_xxx..."}
]
# 发送更新交易
update_tx = client.name_update(
account=account,
name=domain_name,
pointers=pointers,
name_ttl=50000, # 域名 TTL
client_ttl=500 # 指针缓存 TTL
)
print(f"更新交易: {update_tx.hash}")
# 验证更新
name_info = client.get_name_entry_by_name(domain_name)
print(f"\n更新后的指针:")
for pointer in name_info.pointers:
print(f" {pointer.key}: {pointer.id}")
任务 8.6: 域名解析
def resolve_name(name: str, pointer_key: str = "account_pubkey") -> str:
"""解析域名到地址"""
try:
name_info = client.get_name_entry_by_name(name)
for pointer in name_info.pointers:
if pointer.key == pointer_key:
return pointer.id
return None
except Exception as e:
raise ValueError(f"无法解析域名 {name}: {e}")
# 测试域名解析
domains_to_resolve = ["aeternity.chain", "test.chain"]
for domain in domains_to_resolve:
try:
address = resolve_name(domain)
if address:
print(f"{domain} → {address}")
else:
print(f"{domain} → 无 account_pubkey 指针")
except ValueError:
print(f"{domain} → 未注册")
任务 8.7: 转让域名
# 新所有者
new_owner = Account.generate()
# 发送转让交易
transfer_tx = client.name_transfer(
account=current_owner,
name=domain_name,
recipient=new_owner.get_address()
)
print(f"转让交易: {transfer_tx.hash}")
# 验证转让
name_info = client.get_name_entry_by_name(domain_name)
print(f"新所有者: {name_info.owner}")
print(f"转让成功: {name_info.owner == new_owner.get_address()}")
域名指针类型
| 指针类型 | 说明 | 示例 |
|---|---|---|
account_pubkey | 账户地址 | ak_xxx... |
contract_pubkey | 合约地址 | ct_xxx... |
oracle_pubkey | 预言机地址 | ok_xxx... |
channel | 状态通道 | ch_xxx... |
指针用途
# 转账时使用域名
client.spend(account, "alice.chain", "1AE") # 而非 ak_xxx...
# 调用合约
manager.at_address("mycontract.chain") # 而非 ct_xxx...
知识检查点
完成 Day 8 后,你应该能够: