本文介绍了Fault Dispute Game (FDG) 的机制,这是一种通过迭代二分执行轨迹来验证根声明有效性的争议解决游戏。参与者通过提出声明来缩小执行轨迹,直至争议点为单个状态转换。游戏依靠虚拟机(VM)来验证声明的有效性,并最终通过对声明的争议情况来确定胜者。文章还涉及了参与者、移动类型、游戏时钟和最终的解决方案等关键概念。
<!-- START doctoc generated TOC please keep comment here to allow auto update --> <!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE --> 目录
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
故障争议游戏 (FDG) 是一种特定类型的 争议游戏,它通过迭代地将执行轨迹二分到单个指令步骤来验证根声明的有效性。 它依赖于虚拟机 (VM) 来伪造在单个指令步骤中提出的无效声明。
参与者,即玩家,通过提出对 FDG 中其他声明提出异议的声明来与游戏互动。 做出的每个声明都会缩小执行轨迹,直到争议的根源是单个状态转换。 一旦达到时间限制,争议游戏就会根据已提出但未被质疑的声明进行解决,以确定游戏的获胜者。
这是一个状态转换函数 (STF),它接受一个前置状态并计算后置状态。 VM 可能会访问 STF 期间引用的数据,因此,它也接受该数据的证明。 通常,前置状态包含对证明的承诺,以验证所引用数据的完整性。
从数学上讲,我们将 STF 定义为 $VM(S_i,P_i)$,其中
这是一个预图像数据存储。 VM 经常使用它来在其 STF 期间读取外部数据。 在成功执行 VM STF 之前,可能需要预先将相关数据加载到 PreimageOracle 中。 基于密钥检索这些预图像的方法因特定 VM 而异。
执行轨迹 $T$ 是一个序列 $(S_0,S_1,S_2,...,S_n)$,其中每个 $Si$ 都是 VM 状态,并且 对于每个 $i$,$0 \le i \lt n$,$S{n+1} = VM(S_i, P_i)$。 每个执行轨迹都有一个唯一的起始状态 $S_0$,该状态预设为 FDG 实现。 我们将此状态称为 ABSOLUTE_PRESTATE。
声明断言一个执行轨迹。 这表示为 ClaimHash
,一个对轨迹中最后一个 VM 状态的 bytes32
承诺。 FDG 使用根声明初始化,该声明承诺整个执行轨迹。 正如我们稍后将看到的,可以有多个声明,承诺 FDG 中的不同状态。
有向无环图 $G = (V,E)$ 表示声明之间的关系,其中:
游戏树是位置的二叉树。 DAG 中的每个声明都引用游戏树中的一个位置。
游戏树具有最大深度 MAX_GAME_DEPTH
,该深度预先设置为 FDG 实现。
因此,游戏树包含 $2^{d-1}$ 个位置,其中 $d$ 是 MAX_GAME_DEPTH
(除非 $d=0$,在这种情况下只有 1 个位置)。
位置表示声明在游戏树中的位置。 这由“广义索引”(或 gindex)表示,其中高位是树中的级别,其余 位是唯一的位模式,允许为树中的每个节点提供唯一的标识符。
位置 $n$ 的 gindex 可以计算为 $2^{d(n)} + idx(n)$,其中:
游戏树最深层的位置对应于执行轨迹中的索引。 游戏树中较高的位置也覆盖相对于当前位置的最深、最右侧的位置。 我们将这种覆盖称为位置的轨迹索引。
这意味着声明提交到的执行轨迹终止的索引与其位置的轨迹索引相同。 也就是说,对于给定的轨迹索引 $n$,其 ClaimHash 对应于轨迹中的第 $S_n$ 个状态。
请注意,可以有多个位置覆盖相同的轨迹索引。
这是一个不可变的,预设为 FDG 实现,表示游戏持续时间。
游戏包括两种类型的参与者(或玩家):挑战者和防御者。 这些玩家被分成不同的团队,每个团队都采用不同的策略来与游戏互动。 团队成员在游戏结果方面拥有共同的目标。 玩家主要通过行动与游戏互动。
行动是对声明的执行轨迹的挑战,必须包括断言不同轨迹的替代声明。 行动可以是攻击或防御,并通过添加节点和针对有争议的声明的边来更新 DAG。
最初,添加到 DAG 的声明是未被反驳的(即不是被反驳的)。 一旦某个行动针对某个声明,该声明 就被认为是已被反驳的。 声明的状态 — 无论它是否被反驳 — 有助于确定其有效性,并最终确定 游戏的获胜者。
不同意某个声明时采取的逻辑行动。
游戏树中相对于节点 n
的相对攻击位置的声明承诺
n
的声明轨迹的一半。
相对于节点的位置的攻击位置可以通过将其 gindex 乘以 2 来计算。
为了说明这一点,这是一个游戏树,高亮显示了对位于 6 的声明的攻击。
攻击位置 6 的节点会创建一个位于 12 的新声明。
当你同意声明及其父声明时,针对声明的逻辑行动。
游戏树中相对于节点 n
的防御位置承诺 n + 1 轨迹范围的前半部分。
请注意,因此,某些节点可能永远不会存在于游戏树中。 但是,它们不是必需的,因为这些节点在树内具有互补、有效的位置,具有相同的轨迹索引。 例如,gindex 为 5 的位置与 gindex 为 2 的另一个位置具有相同的轨迹索引。 我们可以验证所有轨迹索引在游戏中都有有效的移动:
同一位置可能存在多个声明,只要它们的 ClaimHash
是唯一的。
每个行动都会以严格递增的深度向游戏树添加新声明。
一旦声明位于 MAX_GAME_DEPTH
,反驳此类声明的唯一方法就是step。
在 MAX_GAME_DEPTH
,声明的位置对应于执行轨迹的索引。
正是在此时,FDG 能够查询 VM 以确定声明的有效性,
通过检查它们承诺的状态。
这是通过将 VM 的 STF 应用于声明承诺的状态来完成的。
如果 STF 后置状态与声明的状态不匹配,则挑战成功。
/// @notice 通过链上故障证明处理器执行指令步骤。
/// @dev 此函数应指向故障证明处理器,以便在
/// 链上执行故障证明程序中的步骤。 故障证明
/// 处理器合约的接口应符合 `IBigStepper` 接口。
/// @param _claimIndex `claimData` 中被挑战声明的索引。
/// @param _isAttack 该步骤是攻击还是防御。
/// @param _stateData 该步骤的 stateData 是给定
/// prestate 处声明的预图像,如果操作是攻击,则该预图像位于 `_stateIndex` 处,如果
/// 操作是防御,则该预图像位于 `_claimIndex`处。 如果该步骤是对第一条指令的攻击,则它是
/// 故障证明VM的绝对前置状态。
/// @param _proof 访问 VM 的 merkle 状态树中的内存节点的证明。
function step(uint256 _claimIndex, bool isAttack, bytes calldata _stateData, bytes calldata _proof) external;
与移动类似,有两种方法可以对声明执行步骤:攻击或防御。 这些决定了 VM STF 的前置状态输入和预期的输出。
FDG 步骤处理 VM 的输入并断言预期的输出。
成功证明无效的后置状态(攻击时)或前置状态(防御时)的步骤是
对有争议的声明的成功反驳。
玩家通过提供攻击和状态数据的指标(包括任何证明)与 step
交互,
该指标对应于预期的 pre/post 状态(取决于它是攻击还是防御)。
FDG 将断言现有声明承诺玩家提供的状态数据。
某些步骤(VM 状态转换)需要 PreimageOracle
提供外部数据。
为了确保成功的状态转换,玩家应提前提供此数据。
FDG 提供了以下接口来管理加载到 PreimageOracle
的数据:
/// @notice 将请求的本地数据发布到 VM 的 `PreimageOralce`。
/// @param _ident 要发布的本地数据的标识符。
/// @param _partOffset 要发布的数据的偏移量。
function addLocalData(uint256 _ident, uint256 _partOffset) external;
addLocalData
函数将预图像的部分加载到 VM 的 PreimageOracle
。
玩家使用它来确保预图像部分在步骤期间可用于 VM。
挑战者试图反驳根声明,而防御者旨在支持它。 这两种类型的参与者都将采取相应的行动来支持他们的团队。 对于挑战者来说,这意味着 攻击根声明并反驳位于游戏树中偶数深度的声明。 防御者通过反驳位于奇数深度的声明来做相反的事情。
任何一方的玩家都有动力支持其队友的行动。 这包括反驳对其团队提出的声明的争议(假设这些声明是诚实的)。 未被反驳的声明可能会导致失败,这将在解决中稍后解释。
游戏中的每个声明都有一个时钟。 声明继承了其祖父声明在 DAG 中的时钟(等等)。 类似于国际象棋计时器,它可以跟踪每个团队进行移动的总时间, 从而防止延迟。 对特定声明采取行动会恢复有争议声明的时钟,并暂停新添加的时钟。
一旦有争议声明的父声明的时钟
超过 GAME_DURATION
的一半,就不再可能对特定声明采取行动。 在这一点上,声明的时钟已过期。
解决 FDG 确定哪个团队赢得了比赛。 为此,我们使用内部子游戏结构。 游戏中的每个声明都是其自身子游戏的根。 这些子游戏被建模为嵌套的 DAG,每个 DAG 的最大 深度为 1。 为了使声明被认为是反驳的,其子声明中必须只有一个是未被反驳的。 在解决所有其子级之前,子游戏 也无法解决,这些子级本身就是子游戏,并且潜在对手的国际象棋计时器已用完。 因为每个声明都是其自身子声明的根, 所以真相通过自下而上地解决每个单独的子游戏向上渗透到根声明。
在像下面这样的游戏中,我们可以从最深的子游戏中向上解决。 在这里,我们将 b0
解决为未反驳,a0
通过从它们最深的子级向上走来解决为反驳,并且一旦根游戏的所有子级都递归地解决,我们可以将根解决为由于 b0
保持未反驳而被反驳。
<!-- https://gist.github.com/clabby/e98bdd80ef3c038424f3372b70e34e08 --> <!-- markdownlint-disable no-inline-html --> <https://github.com/ethereum-optimism/optimism/assets/8406232/d2b708a0-539e-439d-96bd-c2f66f3a45f8>
另一个例子是这个游戏,它具有稍微不同的结构。 在这里,由于 b0
保持未反驳,因此根声明也将被反驳。
<!-- digraph G { rankdir=LR newrank=true node [shape=plaintext] subgraph cluster_01 { label = "Legend"; key [label=<<table border="0" cellpadding="2" cellspacing="0" cellborder="0"> <tr><td align="right" port="i1">bisection</td></tr> <tr><td align="right" port="i2">resolution</td></tr> </table>>] key2 [label=<<table border="0" cellpadding="" cellspacing="0" cellborder="0"> <tr><td port="i1"> </td></tr> <tr><td port="i2"> </td></tr> </table>>] key:i1:e -> key2:i1:w [color=green] key:i2:e -> key2:i2:w [color=coral1, style=dotted] } subgraph cluster_0 { color=cornflowerblue; node [style=filled]; a0 -> a1 [color=green]; a1 -> a0 [color=coral1, style=dotted]; subgraph cluster_0_0 { label = "subgame #5"; color=purple; a1 -> a2 [color=green]; a2 -> a1 [color=coral1, style=dotted]; subgraph cluster_0_1 { label = "subgame #6"; color=magenta; a2 -> a3 [color=green]; a3 -> a2 [color=coral1, style=dotted]; a2 -> a4 [color=green]; a4 -> a2 [color=coral1, style=dotted]; subgraph cluster_0_2 { label = "subgame #7"; color=lightpink; a3 } subgraph cluster_0_3 { label = "subgame #8"; color=lightpink; a4 -> a5 [color=green]; a5 -> a4 [color=coral1, style=dotted]; subgraph cluster_0_4 { label = "subgame #9"; color=palegreen; a5 } } } } label = "subgame #4"; } subgraph cluster_1 { node [style=filled]; label = "subgame #1"; color=cornflowerblue b0 -> b1 [color=green]; b1 -> b0 [color=coral1, style=dotted]; subgraph cluster_1_0 { label = "subgame #2"; color=purple; b1 -> b2 [color=green]; b2 -> b1 [color=coral1, style=dotted]; subgraph cluster_1_1 { label = "subgame #3"; edge [style=invis] color=magenta; b2 } } } Root -> a0 [color=green]; Root -> b0 [color=green]; a0 -> Root [color=coral1, style=dotted]; b0 -> Root [color=coral1, style=dotted]; Root [shape=Mdiamond]; } -->
<!-- markdownlint-disable no-inline-html --> <p align="center"> <img src="https://github.com/ethereum-optimism/optimism/assets/8406232/9b20ba8d-0b64-47b3-9962-5533f7eb4ef7" width=60%> </p>
鉴于这些规则,玩家有动力迅速采取行动来挑战所有不诚实的声明。
每个行动都会将执行轨迹一分为二,最终会到达 MAX_GAME_DEPTH
,在那里可以确定性地解决争议。
不诚实的玩家通过向后归纳被阻止参与,
因为无效的声明不会保持未被反驳。 可以通过要求
声明被绑定,同时使用不诚实声明的绑定来奖励游戏获胜者,从而为游戏添加更多激励。
- 原文链接: github.com/ethereum-opti...
- 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!