从零到英雄:OP Stack 故障证明系列 3 [ENG]

本文是Tokamak Network关于OP Stack故障证明系列的第三篇文章,主要介绍了Optimism的Fault Dispute Game (FDG),探讨了如何使用链上(MIPS.sol)和链下(MIPSEVM)组件解决Layer 2状态纠纷。文章还详细阐述了FDG中的提议者、挑战者的角色,PreimageOracle的数据管理,以及确保完整性和安全性的激励结构,并对二分博弈进行了说明。

从零到英雄:OP Stack 欺诈证明系列 3 [ENG]

欺诈争议游戏 (FDG) / 二分游戏概述

Tokamak Network 旨在为当前对 Optimism 和 Ethereum 生态系统演进感兴趣的开发者提供有价值的信息。

来源: Oplabs 链接

这篇文章是 'OP Stack 完整版欺诈证明系列' 的第三篇,该系列由 Tokamak Network 计划发布的 3 篇文章组成。在本文中,我们探讨 Optimism 的欺诈证明系统如何使用链上 (MIPS.sol) 和链下 (MIPSEVM) 组件解决 Layer 2 状态争议。我们涵盖了提议者和挑战者的角色,PreimageOracle 的数据管理,以及确保完整性和安全性的激励结构。此外,我们还将解释将争议分解为单个指令以进行精确验证的二分游戏。

💡 鉴于该系列的互连性,我们建议按顺序阅读文章,以获得连贯的理解。

系列 1. Cannon 的欺诈证明系统: 在本文中,我们将介绍 Optimism 的欺诈证明程序,并解释其多重证明架构如何增强 Layer 2 的安全性和可靠性。该程序集成了强大的欺诈证明机制,以确保准确的状态转换和高效的争议解决。[ LINK ]

系列 2. Cannon 的欺诈证明系统: 在本文中,我们概述了 Cannon (Optimism 的 FPVM),它通过链下执行和链上验证来确保 Layer 2 状态转换的完整性。MIPS.sol 、_ MIPSEVM PreimageOracle 等关键组件协同工作,以实现高效的争议解决。[ LINK ]

什么是争议游戏?

图:‘争议游戏’可视化。

争议游戏是争议协议的核心原语,它对一个简单的状态机进行建模。它从对任何可以被质疑有效性的信息的 32 字节承诺开始。由实施者定义的解决函数确定此承诺是真还是假。OP Stack 的第一个实现 FaultDisputeGame 是无需许可的,其解决函数由在模拟 VM 上执行的欺诈证明程序的结果确定。

争议游戏依赖于两个基本属性:

  1. 激励兼容性: 该系统惩罚虚假声明并奖励真实声明,从而确保公平参与。
  2. 解决: 每个游戏都有一种机制来明确地验证或使根声明无效。

💡 可以通过 DisputeGameFactory 创建、管理和升级不同类型的争议游戏,从而实现创新功能,例如聚合证明系统以及争议 L2 状态之外的各种事项(例如链上二进制验证)的能力。

争议游戏在 Optimistic Rollup 安全中的作用

  • 争议游戏涉及多个当事方对声明的有效性提出异议,在 optimistic rollup 中,关于 Layer 2 网络状态的声明是为了方便 Layer 1 提款而提出的。
  • Layer 2 网络的安全取决于挑战和质疑欺诈性提款的能力。争议游戏接口支持多个实现,类似于拥有多个协议客户端,从而确保一个实现中的错误不会损害整个共识过程。

基本游戏规范

类型

图:LibUDT.sol (来源: github 链接)

  • Claim (声明): 表示声明的 32 字节哈希或唯一标识符。
  • Hash (哈希): 通用哈希的自定义类型。
  • Timestamp (时间戳): 专用的时间戳类型。
  • GameType (游戏类型): 表示正在玩的游戏的类型。
  • GameId (游戏ID): 表示打包的 4 字节游戏 ID、8 字节时间戳和 20 字节地址。

[0, 32]: 游戏类型

[32, 96]: 时间戳

[96, 256]: 地址

GameTypes 库

图:Types.sol (来源: github 链接)

  • CANNON (ID 0): 一种使用 Cannon VM 的争议游戏类型。
  • PERMISSIONED_CANNON (ID 1): 一种也使用 Cannon VM 的许可争议游戏类型。
  • ASTERISC (ID 2): 一种使用 ASTERISC VM 的争议游戏类型。
  • FAST (ID 254): 一种持续时间短的争议游戏类型,旨在测试提款,不用于生产用途。
  • ALPHABET (ID 255): 一种使用 Alphabet VM 的争议游戏类型,也不用于生产用途。

GameStatus 枚举

图:service.ts (来源: github 链接)

表示游戏的当前状态:

  • IN_PROGRESS: 游戏正在进行中。
  • CHALLENGER_WINS: 挑战者成功质疑了 rootClaim
  • DEFENDER_WINS: 防御者的 rootClaim 未受到质疑。

争议游戏工厂的关键点

1. 声明提交:

  • 提议者对 layer two 网络的状态提出声明。

2. 设置初始化保证金:

图:IDisputeGameFactory.sol (来源: github 链接)

  • 工厂确保设置初始化争议游戏所需的保证金。这是通过可以由所有者调用的 setInitBond 函数来完成的,以设置用于初始化游戏类型的保证金(以 wei 为单位)。
  • 当保证金更新时,会发出 InitBondUpdated 事件,其中包含游戏类型和新保证金金额的详细信息。

💡 setInitBond InitBondUpdated 是管理设置的一部分

3. 争议游戏初始化:

图:IDisputeGameFactory.sol (来源: github 链接)

  • 争议游戏由 DisputeGameFactory 初始化,该工厂创建一个新的 DisputeGame 合约。这涉及在工厂上调用 create 函数,传入 _gameType_rootClaim 和任何 _extraData
  • 工厂会发出一个 DisputeGameCreated 事件,其中包含有关争议游戏的详细信息。

图:IDisputeGame.sol (来源: github 链接)

  • 保证金确保参与者(提议者和挑战者)在有争议的游戏的结果中拥有经济利益。这有助于阻止恶意声明和挑战。
  • 此保证金由合约持有,以确保提议者在争议中拥有经济利益。

4. 争议游戏配置:

图:DisputeGame.sol (来源: github 链接)

  • 新的 DisputeGame 合约通过调用其 initialize 函数来初始化。这设置了游戏参数,包括根声明、游戏创建者、L1 区块父哈希和提供的任何额外数据。

5. 游戏监控:

  • 挑战者监听 DisputeGameCreated 事件以识别新的争议并相应地参与。
  • 挑战者也可能需要支付保证金才能参与争议。

💡 挑战者有两个选择:他们可以使用 DisputeGameCreated 事件来启动一个新游戏,或者他们可以通过监控现有的 DisputeGame 合约并以挑战者的身份加入争议来参与已创建的争议。

争议游戏发生……

6. RootClaim 处理(游戏解决)

图:IDisputeGameFactory.sol (来源: github 链接)

  • _rootClaim 是 DisputeGame 的根声明。
  • 争议游戏的 _rootClaim 可能被创建者同意或不同意。

图:IDisputeGame.sol (来源: github 链接)

  • IDisputeGame 的 resolve 函数处理。

条件: 只有在游戏状态为 IN_PROGRESS 时才能调用。

目的: 将游戏状态标记为 CHALLENGER_WINSDEFENDER_WINS,并返回已解决的游戏状态。

返回: 解决后的游戏状态。

7. 最终确定

  • 根据最终的游戏状态,保证金将授予必要的各方(例如,提议者或挑战者)。如果提议者获胜,则保证金将返还给提议者,如果提议者失败,则将被没收给挑战者。

💡 争议游戏工厂维护所有已创建的争议游戏的记录。这包括跟踪游戏数量、游戏类型、时间戳和实施细节。

‘clones-with-immutable-args’ 模式

clones-with-immutable-args 模式在争议游戏初始化阶段使用。这种方法用于创建轻量级代理合约(称为克隆),这些合约使用特定的不可变参数进行定制。

GameType 实现

图:LibClone.sol (来源: github 链接)

  • 每个 GameType(例如,CANNONASTERISC)都有一个对应的实现合约,该合约已部署在工厂中。
  • 创建新游戏时,工厂使用 clones-with-immutable-args 模式生成特定 GameType 的实现合约的克隆。

与 GameTypes 的连接

  • 特定实现: 每个 GameType 都有其自己的实现合约。
  • 争议参数: 初始化特定 GameType 的争议游戏时,工厂会创建一个对应的实现合约的克隆,并将争议特定的参数嵌入为不可变参数。

创建克隆:

  • clone(address implementation, bytes memory data):部署具有编码在 data 中的不可变参数的实现的克隆。
  • clone(uint256 value, address implementation, bytes memory data):与上述类似,但在部署期间也会存入 value ETH。

角色和优势:

  • 定制克隆: 每个克隆都使用特定的不可变参数进行定制,使其能够根据特定争议的参数进行操作。
  • 共享核心逻辑: 克隆共享来自预先部署的实现合约的相同核心逻辑,从而确保一致性。
  • 效率和成本效益: 这种方法使新游戏实例的部署更加高效且具有成本效益,因为它避免了每次都重新部署整个合约逻辑。

💡 总之,clones-with-immutable-args 模式允许通过克隆现有实现合约和嵌入特定参数来高效且有针对性地部署争议游戏实例。这确保了每个游戏都根据其指定的 GameType 运行,而无需每次都重新部署核心逻辑。

二分游戏 / 欺诈争议游戏 (FDG)

关键概念:

1. 虚拟机 (VM)

定义:虚拟机 (VM) 是一种计算模型,它使用状态转换函数 (STF) 来处理状态转换,以从给定的预状态计算后状态。

  • Pre-state (预状态):转换之前的初始状态。

  • Optional Proof (可选证明):发生转换所需的证明。

  • Post-state (后状态):转换后的结果状态。

  • Mathematical Definition (数学定义):这意味着 VM 在给定预状态和证明的情况下,计算后状态。
  • Data Integrity (数据完整性):预状态包含对证明的承诺,通过验证转换过程来确保数据完整性。

2. PreimageOracle

定义PreimageOracle 是虚拟机 (VM) 用于在状态转换函数 (STF) 期间访问外部数据的预图像数据存储,需要预先加载相关数据并利用特定于每个 VM 的基于密钥的检索方法。

  • Preloading Data (预加载数据):在执行 STF 之前,必须使用相关数据预加载 PreimageOracle
  • Key-based Retrieval (基于密钥的检索):该方法因特定 VM 而异。

3. Execution Trace (执行轨迹)

定义:这是一个 VM 状态序列,显示了从初始状态到最终状态的转换,表示 VM 从一个状态移动到下一个状态的过程。

  • 这是在发生任何转换之前 VM 状态的起点。

  • VM 处理初始状态的第一次转换。

  • VM 处理状态的第二次转换。

  • n 次转换后的最终状态。

4. Claims (声明)

图:‘争议游戏’可视化。

定义:断言给定指令处的输出根或 FPVM 的状态。

  • 声明表示执行轨迹中的单个步骤,每个声明对应于执行轨迹中的单个状态转换。
  • 表示为 Hash 类型 (bytes32),它可以是输出根或对轨迹中最后一个 VM 状态的承诺(每个执行轨迹)。
  • SPLIT_DEPTH + 1 处的执行轨迹子游戏从一个承诺两个连续输出根(块 n -> 块 n+1)之间的整个执行轨迹的声明开始。

5. Anchor State and Initialization (锚定状态和初始化)

定义:锚定状态(或锚定输出根)是假定为有效的先前的输出根。

Initialization (初始化)

  • 欺诈争议游戏 (FDG) 使用锚定状态进行初始化。执行发生在锚定状态和声明的输出根之间。
  • 锚定状态是从 Anchor State Registry 合约中检索的,FDG 的初始锚定状态是 L2 的创世状态。

6. Subgame (子游戏)

图:‘子游戏’可视化。

定义:子游戏是深度为 1 的有向无环图 (DAG),其中 DAG 的根是 Claim,子节点是反驳根的 Claim。这种结构代表了双方对单个信息的根本争议。

7. Game Tree (游戏树)

图:‘游戏树’可视化。

定义:一个位置的二叉树,DAG 中的每个声明都引用游戏树中的一个位置。

深度

  • Split Depth (拆分深度):有关输出根的声明可能发生的最大深度。
  • Maximum Depth ( MAX_GAME_DEPTH )(最大深度):预设为 FDG 实现,定义了游戏树的总深度。

结构体:

  • 游戏树包含 2^{d+1} — 1 个位置,其中 d 是 MAX_GAME_DEPTH(除非 d=0,在这种情况下只有 1 个位置)。

8. Position (位置)

定义:声明在游戏树中的位置由“广义索引”(gindex) 表示。

Generalized Index (gindex) (广义索引):

  • 高阶位表示树中的级别。
  • 剩余位形成唯一的位模式,为每个节点创建一个唯一的标识符。

Calculation (计算):

  • 位置 n 的 gindex 计算为 2^(d(n))+idx(n),其中:
  • d(n):返回游戏树中位置深度的函数。
  • idx(n):返回其深度处位置的索引(从左侧开始)的函数。

9. MAX_CLOCK_DURATION

定义:跟踪每个团队进行移动的总时间,类似于通过确保及时移动来防止延迟的国际象棋时钟。(FDG 实现的不可变预设。)

Functionality (功能)

  • 移动会恢复有争议的声明的时钟,并暂停新添加的声明的时钟。
  • 如果声明的时钟剩余时间少于 CLOCK_EXTENSION 秒,则会给予其正好 CLOCK_EXTENSION 秒的时间。这有助于确保有足够的时间进行诚实的移动。MAX_GAME_DEPTH

💡 声明时钟是与欺诈争议游戏中每个声明关联的计时器,用于跟踪团队对该特定声明进行移动的时间。此时钟机制确保团队及时移动并防止游戏中的延迟。

  • 对于作为执行轨迹二分法根的声明,如果其声明时钟剩余时间少于 CLOCK_EXTENSION 秒,则将获得正好 CLOCK_EXTENSION \* 2 秒的时间。这段额外的时间允许完成必要的链下计算。

Expiration (到期)

  • 一旦父声明的时钟累积了 MAX_CLOCK_DURATION 秒,就无法再对声明进行移动,从而导致声明的时钟到期。

💡 在最近对区块链安全机制的评估中,Offchain Labs 和 Optimism 之间的合作中发现了一些特定漏洞,尤其是在其欺诈证明系统中对计时器的管理。这些漏洞突出了在设计需要多方参与的系统时所面临的挑战,这可能会使计时器等关键要素的管理变得复杂。 更多详情。

关键游戏机制

1. 初始化

  • Root Claim (根声明):FDG 从根声明开始,该声明是与特定 L2 区块号的 Layer 2 (L2) 状态对应的输出根。此根声明由防御者断言。

2. Actors Involved (参与者)

Players (玩家):主要有两种类型的玩家:挑战者和防御者。

  • Challengers (挑战者):旨在通过证明其无效来质疑根声明。
  • Defenders (防御者):旨在证明根声明有效。

3. Game Tree and Claims (游戏树和声明)

  • Game Tree (游戏树):一种二叉树结构,其中每个节点代表一个声明。每个声明都承诺执行轨迹中的特定状态。
  • Execution Trace (执行轨迹):代表从初始状态 (ABSOLUTE_PRESTATE) 到有争议状态的转换的 VM 状态序列。

4. Making Moves (移动)

Moves (移动):玩家通过移动与游戏互动,这些移动是对现有声明的攻击或防御。每次移动都会以严格递增的深度向游戏树添加新的声明。一旦一项声明达到了 MAX_GAME_DEPTH,质疑它的唯一方法是通过步骤。

图:‘攻击’可视化

Attack (攻击):当你不同意某一项声明时,为了质疑该声明而采取的行动。

  • 相对于游戏树中的节点 (n) 的攻击位置通过将其 gindex 乘以 2 来计算。
  • 攻击位置的新声明承诺原始声明范围的一半轨迹。
  • 如果一项声明位于游戏树中的节点 5:

- 攻击这一项声明会移动到节点 10 的新位置。

- 节点 10 中的这一项新声明会质疑节点 5 中的声明。

图:‘防御’可视化

Defense (防御):当你同意一项声明及其父项时,为了支持一项声明而采取的行动。

  • 通过提供防御先前状态的执行轨迹来支持一项声明。
  • 相对于游戏树中的节点 (n) 的防御位置承诺 (n+1) 轨迹范围的前半部分。
  • 在节点 10 上防御一项声明:

- 防御位置在节点 11 (5 + 1) 处。

- 承诺节点 11 轨迹范围的前半部分。

5. Bisection (二分)

Bisection Process (二分过程):游戏通过迭代地二分有争议的状态范围来推进,直到争议缩小到单个 VM 指令步骤。

  • Split Depth (拆分深度):定义了有关输出根的声明可能发生的最大深度。在此深度以下,游戏专注于二分执行轨迹。
  • MAX_GAME_DEPTH:当二分到达最大游戏深度时,声明的位置对应于执行轨迹中的特定索引。此时,游戏可以查询 VM 以确定声明的有效性。

6. Stepping (步进)

Step (步骤):与移动类似,有两种方法可以对声明进行步进:攻击或防御。这些步骤确定了 VM STF 的预状态输入和预期输出。

Attack Step (攻击步骤):通过提供预状态来挑战一项声明,证明一种无效的状态转换。

  • Input (输入):使用执行轨迹中的先前状态。
  • Output (输出):期望有争议的声明的状态。
  • Requirement (要求):DAG 中必须存在一项承诺输入状态的现有声明。

Defense Step (防御步骤):通过证明它是一项无效的攻击来挑战一项声明,从而防御有争议的父项的声明。

  • Input (输入):使用有争议的声明的状态。
  • Output (输出):期望执行轨迹中的下一个状态。
  • Requirement (要求):DAG 中必须存在一项承诺预期输出状态的现有声明。

FDG Step Mechanics (FDG 步进机制)

  • Handling Inputs (处理输入):FDG 步骤管理 VM 的输入并断言预期输出。
  • 成功证明无效后状态(在攻击时)或预状态(在防御时)的步骤是对有争议的声明的成功反击。

7. Interaction with PreimageOracle (与 PreimageOracle 交互)

在争议游戏的背景下,VM 状态转换依赖于 PreimageOracle 提供的外部数据。玩家必须事先提供此数据,以确保在争议解决过程中成功进行状态转换。

图:IFaultDisputeGame.sol (来源: github 链接)

Function (函数)addLocalData

  • 将本地数据加载到 VM 的 PreimageOracle 中。

Parameters (参数)

  • _ident:要发布的本地数据的本地标识符。
  • _execLeafIdx:执行子游戏中需要本地数据进行步进的叶子声明的索引。
  • _partOffset:要发布的数据的偏移量。

Data Identifiers (数据标识符):验证和处理争议期间的 VM 状态转换需要特定的数据。

1. 提案时父 L1 头哈希。

  • 这是创建提案那一刻的Layer1链的头块的哈希。它用作该特定时间Layer1区块链状态的参考点。

2. 启动输出根哈希(承诺块 # n)。

  • 这是处理块 # n 后输出状态的根哈希。它代表Layer2网络在块编号 n 处的状态。

3. 有争议的输出根哈希(承诺块 # n + 1)。

  • 这是有争议的输出状态的根哈希,对应于块 # n + 1。它显示了有争议的块之后的Layer2网络的状态。

4. 有争议的 L2 块号(块 # n + 1)。

  • 这是Layer2中存在争议的块的编号,它遵循块编号 n 。

5. L2 链 ID。

  • 这是发生争议的特定Layer2链的标识符。如果正在使用多个Layer2网络,它有助于区分它们。

Submitting Preimages (提交预图像)

  • Small Preimages (小预图像):逐个提交。
  • Large Preimages (大预图像):通过在多个交易中流式传输来提交。

Steps for Large Preimage Proposals (大型预图像提案的步骤)

  • 大型预图像提案允许提交者通过多个交易流式传输大型预图像,并承诺 keccak256 函数的中间状态。

图:PreimageOracle.sol (来源: github 链接)

Function (函数)hashLeaf

  • 返回要添加到预图像提案 Merkle 树的叶子哈希。

Parameters (参数)

  • input:输入的 136 字节块。
  • blockIndexinput 对应的块的索引。
  • stateCommitment:吸收和置换 input 后完整 5x5 状态矩阵的哈希。
  • Requirement (要求):输入的大小必须正好是 keccak256 速率的大小(136 字节)。

8. Finalization (最终确定)

Anchor State Registry (锚定状态注册表):一旦游戏得到解决,最终状态就会报告给 Anchor State Registry。如果游戏解决结果有利于防御者,并且新状态比当前锚定状态更新,则注册表会更新锚定状态。

9. Challenge Period (挑战期)

一旦发布完整的预图像和所有中间状态承诺,挑战期就开始了。在此期间,挑战者可以验证链上构建的 Merkle 树的完整性。以下是所涉及的步骤和过程的详细分解:

Steps (步骤)

1. 为商定的预状态叶子创建 Merkle 证明。

  • 生成一个 Merkle 证明,以证明将商定的预状态叶子包含在 Merkle 树中。此证明用于验证任何有争议的转换之前的初始状态。

2. 为有争议的后状态叶子创建 Merkle 证明。

  • 为有争议的后状态叶子生成一个 Merkle 证明。此证明对于验证有争议的转换后的状态是必要的,以确保它与声明的结果相匹配。

3. 计算商定的预状态下的状态矩阵。

  • 计算商定的预状态下的状态矩阵。这涉及计算导致有争议的转换的中间状态和转换。

Submission (提交):将所有生成的数据(包括 Merkle 证明和计算的状态矩阵)提交给 PreimageOracle。此提交提供了链上验证所需的必要信息。

Validation (验证)

1. SHA3 排列执行:

  • 提交的数据触发了 SHA3 排列的链上执行。此过程涉及对状态矩阵和其他相关数据进行哈希处理,以生成最终哈希。

2. Hash Comparison (哈希比较):

  • 将 SHA3 排列的结果哈希与提案者的声明哈希进行比较。此比较确定提案的有效性:
  • Match (匹配): 如果哈希与提案者的声明相匹配,则提案仍然有效,并且可以继续进行最终确定。
  • Mismatch (不匹配): 如果哈希不匹配,则该提案将被标记为已挑战。

3. Finalization (最终确定):

  • 如果在挑战期结束后没有挑战,则可以完成提案。然后,与最终提案关联的预图像部分将放入 FPVM(欺诈证明虚拟机)的授权映射中,以便在将来的验证中读取和使用。

总结

初始输出根

  • 输出根是一个哈希,表示在批量执行所有交易后Layer2链的最终状态。

中间执行步骤

  • 执行步骤是 VM 在交易处理期间执行的单个操作或指令。这些步骤提供了 VM 的状态在交易处理期间如何演变的详细的、逐步的说明。每个步骤都会根据执行的操作稍微更改 VM 的状态。

💡 挑战者可以通过将指令标记为不正确来攻击该指令,也可以在不采取任何行动的情况下移动到下一条指令。

底部叶子节点(低于 SPLIT_DEPTH)

  • 底部叶子节点表示欺诈证明游戏中最高粒度级别处的 VM 的哈希状态。
  • 它用作争议中最详细的争论点,封装了在特定执行点 VM 的完整状态(包括内存、寄存器、堆栈等)。
  • 它不关注单个执行步骤,而是关注 VM 在非常特定时间点的整体状态。

当挑战者在争议中到达底部叶子节点(低于 SPLIT_DEPTH)时,确定哈希状态是否正确或涉及特定过程:

  1. Reconstruct the State (重建状态):挑战者需要使用执行轨迹重建 VM 到该点的状态。这涉及将指令从已知的正确状态重放到有争议的哈希状态的点。
  2. Verify State Transitions (验证状态转换):通过逐步验证每个状态转换,挑战者可以确保操作序列导致所讨论的哈希状态。
  3. Compare Hashed State (比较哈希状态):挑战者从重建的状态计算预期的哈希状态,并将其与声明的哈希状态进行比较。
  4. Identify Discrepancies (识别差异):如果预期的哈希状态与声明的哈希状态之间存在不匹配,则挑战者可以证明该哈希状态是不正确的。如果它们匹配,则该哈希状态被认为是正确的。

结论

争议游戏是 OP Stack 欺诈证明系统的一个至关重要且令人兴奋的组成部分,它为去中心化欺诈检测提供了创新的解决方案。它们的模块化设计和激励结构确保了公平和高效的争议解决,为 Superchain 生态系统中强大且可扩展的欺诈检测铺平了道路。

参考

Docs (文档)

Youtube

  • 原文链接: medium.com/tokamak-netwo...
  • 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
aaronlee94
aaronlee94
江湖只有他的大名,没有他的介绍。