Skip to content
On this page

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;
}

核心流程

  1. 注册:支付 REGISTRATION_FEE,名称未占用或已过期,格式合法
  2. 转移:所有者将名称 transfer 给新地址
  3. 解析:设置 resolvedTo 指向目标地址
  4. 续费:支付费用延长期限
  5. 过期重注册:超期后任何人可以重新注册

关键实现细节

  • 名称以 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 确保正确:
    solidity
    names[i] = names[names.length - 1];
    names.pop();
    break;
    
  • 将名称格式检查逻辑分离为 pure 函数方便测试
  • 使用 OpenZeppelin 的 Strings 库辅助字符串比较

7. 技术要点

要点说明
哈希存储keccak256(name) 作为键,保护隐私
时间门控block.timestamp 比较实现过期机制
所有权模式NFT 类似的 ownable 资源模型
swap-and-popO(1) 数组元素删除
pull-over-push 费用注册费直接支付,无需额外提款
格式验证链上逐字节字符检查

Built with AiAda