Appearance
L1-13: Name System(名称系统)
1. 问题
构建一个去中心化的链上名称注册系统——将人类可读的名称映射到以太坊地址,类似于简化版的 ENS(Ethereum Name Service)。核心功能:名称注册(付费)、所有权转移、解析地址设置、名称续费、过期释放。
2. 原因
ENS 是 Web3 最重要的基础设施之一——将 vitalik.eth 这样的名称映射到 0x... 地址。理解名称注册系统的核心模式是构建任何链上身份系统的基础:
- 名称映射:人类可读名 -> 机器地址
- 所有权模型:注册者拥有名称的完全控制权
- 时间锁定:注册有期限,过期后释放给他人
- 费用机制:防止名称抢注和囤积
这个挑战简化了 ENS:不需要多级域名(.eth 后缀由 registry 管理),不需要解析器合约分离,专注于名称所有权管理的核心逻辑。
3. 方案
合约架构
solidity
contract NameSystem {
struct NameRecord {
address owner; // 名称所有者
address resolvedTo; // 解析目标地址
uint256 expiresAt; // 过期时间戳
}
uint256 public constant REGISTRATION_FEE = 0.01 ether;
uint256 public constant REGISTRATION_PERIOD = 365 days;
uint256 public constant MIN_NAME_LENGTH = 3;
uint256 public constant MAX_NAME_LENGTH = 32;
mapping(bytes32 => NameRecord) private _records;
mapping(address => bytes32[]) private _ownedNames;
}
核心流程
- 注册:支付
REGISTRATION_FEE,名称未占用或已过期,格式合法 - 转移:所有者将名称 transfer 给新地址
- 解析:设置
resolvedTo指向目标地址 - 续费:支付费用延长期限
- 过期重注册:超期后任何人可以重新注册
关键实现细节
- 名称以
bytes32(keccak256 哈希)存储,保护隐私并保证固定长度 - 使用
_ownedNames数组追踪每个地址拥有的名称列表 - 名称格式验证:仅允许 a-z, A-Z, 0-9 和
-,长度 3-32 - 转移时使用 swap-and-pop 从旧所有者数组中移除
4. 遭遇的陷阱
4.1 过期名称的竞争条件
两个用户可能在同一区块内尝试注册同一个刚过期的名称。由于 Solidity 交易是原子的,只有一个会成功,另一个的 gas 被浪费。
4.2 _ownedNames 数组无限增长
如果用户频繁注册和转移名称,_ownedNames 数组中的 swap-and-pop 删除必须正确执行。如果 pop 后新的元素恰好是重复的(由于不正确的 swap 逻辑),可能导致名称追踪丢失。
4.3 名称格式验证的性能
_isValidName 中的逐字节检查对于 32 字节名称是 O(32),Gas 可接受。但如果扩展长度上限,字符检查的 Gas 成本线性增长。
4.4 哈希碰撞的虚假安全感
使用 keccak256(name) 作为键意味着两个不同的名称不可能碰撞。但这也意味着无法从哈希反推名称——前端需要维护名称到哈希的映射。
5. 陷阱的原因
5.1
以太坊交易按顺序执行(在单区块内),不存在传统的并行竞争条件。但两个 pending 交易可能被同一个 builder 打包——user A 的注册交易和 user B 的注册交易中只有一个成功。B 为失败交易支付了 gas。
5.2
swap-and-pop 是 O(1) 的数组删除技术,但实现中必须确保:被 pop 的元素确实被移到了被删除的位置,且数组长度正确减一。常见 bug:删除了错误的索引、未 break 循环、或数组 pop 后长度不对。
5.3
Solidity 中没有原生的正则表达式。逐字节比较是唯一的方法。对于大型数据集,考虑在链下验证格式并通过签名提交。
6. 如何解决陷阱
- 前端可以监听
NameRegistered事件来检测注册是否成功,失败时提示用户 - 在
_removeFromOwned中使用 swap-and-pop 确保正确:soliditynames[i] = names[names.length - 1]; names.pop(); break; - 将名称格式检查逻辑分离为 pure 函数方便测试
- 使用 OpenZeppelin 的
Strings库辅助字符串比较
7. 技术要点
| 要点 | 说明 |
|---|---|
| 哈希存储 | keccak256(name) 作为键,保护隐私 |
| 时间门控 | block.timestamp 比较实现过期机制 |
| 所有权模式 | NFT 类似的 ownable 资源模型 |
| swap-and-pop | O(1) 数组元素删除 |
| pull-over-push 费用 | 注册费直接支付,无需额外提款 |
| 格式验证 | 链上逐字节字符检查 |