VIEW THIS AS

Auto mode follows the Route Engine until you choose a viewpoint.

YOU ARE HERE

ROUTE CHECK

CONNECTED TO

WHAT NEXT

Use the canonical route for this room, or HELP if you are unsure.

重建区块链时间锁队列:依赖、取消与操作身份

Three students in school uniforms work through open books at a classroom table, with textbooks and stationery nearby and study notes on the whiteboard behind them.

English version · 区块链学习导航 · 上一篇

区块链学习 · 延伸文章 BCX015

三名学生在教室桌旁一起阅读书本,身边放有课本和文具。
学习场景配图;不代表照片中的学生正在学习区块链。

一个排队操作的等待时间已经结束,仍可能无法执行。它要求的前置操作可能已被取消,也可能有另一个操作完成了相同的业务效果,却使用不同的操作身份。只看绿色的“Ready”标签,无法排除这些情况。

本文根据一段已接受的历史记录,重建一个小型依赖队列。我们追踪两个无害的目录操作,分别检查它们的计时条件与依赖条件,并准确解释取消和重新排程保留或替换了什么。核心问题是:该操作指定的那个前置操作是否完成,而不是以前是否发生过类似的事情。

所有例子都是由可复现Python模型支持的静态纸面转换,不涉及真实地址、钱包、金融资产或实际管理员操作。符号标识不是虚构的以太坊哈希向量;本文也不声称编译或部署了Solidity程序。

选择适合你的阅读路线

本篇目录

  • 要理解主要队列问题,阅读第1—4节,再重建第5—6节中前置操作被取消后的时间线。
  • 要理解操作身份与替代操作错误,重点阅读第2节和第7节。
  • 要检查边界案例和独立复核方法,阅读第8—11节。
  • 要做一次简短自测,先回答第12节,再看参考答案。

如果需要先了解较完整的决策背景,可阅读治理变更流程或从投票到执行的证据检查。本文把调用者权限和已接受的排程证据作为给定事实,以便集中分析依赖队列本身。

返回目录 · 下一节

1. 读队列前先固定上下文与目标

每个案例始终使用同一个虚构网络和同一个时间锁部署。它控制一个无害的目录,只有两个字段:schema初始为1,published初始为false。操作A准备schema 2。操作B发布目录,其目标条件要求schema已经是2。

B还把A声明为准确的前置操作。这个条件与目标的schema检查彼此独立。A没有前置操作。稍后引入第三个标识A2,代表另一次准备操作:它与A产生相同的目标效果,但使用不同的salt。

初始最短排程延迟为10个时间单位。A和B均在时间100完成了被接受的排程,指定延迟均为10,因此记录的就绪时间都是110。我们使用较小的虚构时间,便于检查算术;这些值均高于下文介绍的保留时间戳标记。

每个被描述的调用者都具有相应调用所需的权限。变更具有原子性,并按给定顺序发生。没有回调、批量操作、外部物理效果、隐藏失败、升级或未列出的目标变更。模型中的失败调用既不改变队列,也不改变目录。除非明确说明继续前例,否则每个独立案例都重新开始。

这些前提让我们能够逐项诊断原因。缺少这些条件时,执行失败可能涉及调用者权限、目标行为或另一项依赖。给定记录只是隔离了当前要检查的条件,不表示真实部署中可以省略其他检查。

返回目录 · 下一节

2. 身份属于完整操作

在固定版本的源码对照中,单操作标识由目标地址、value、调用数据、前置操作和salt共同导出。排程时间与所选延迟不属于这个元组。课堂符号A、A2、B分别代表完整且不同的操作元组;本文不计算或展示真实标识。

因此,取消后重新排程完全相同的A,可以保留操作身份,却获得不同的就绪时间。相比之下,即使目标效果相同,改变A的salt也会得到这里明确设定为不同的A2标识。同样,把B的前置操作从A改为A2,会改变这个后续操作本身的身份。

应把网络和部署上下文与标识一起保存。单有操作元组,并不能完整说明应该在哪里读取它的完成记录。本练习不会把另一部署中的完成结果,与当前检查的队列混合使用。

在工作表中,为每个操作建立一行,记录符号ID、效果、准确前置ID、当前计时值或完成标记,以及已接受的历史。不要把人类可读的动作描述当作行的主键。“准备schema 2”是有用的描述,但A和A2都符合这个描述,却仍是不同操作。

这是整个问题的基础:依赖指向一条完成记录,而不是大致相同的意图。如果某个应用希望检查“schema等于2”,那就是另一种条件,需要单独明确规定。

返回目录 · 下一节

3. 读取四种当前状态,不要自创第五种

源码中的状态查询区分Unset、Waiting、Ready和Done。存储的时间戳0表示Unset,保留标记1表示Done。其余时间戳表示就绪时点:仍在未来时为Waiting,到达该时点或已经过去时为Ready。

在初始队列中,A和B在109时都是Waiting,在110时都是Ready。它们的计时条件已满足,但没有哪个操作因此自动成为Done。时间流逝改变的是查询对已存时间戳的解释,不会替系统执行目标调用。

取消是历史中记录的一项动作。对仍处于待处理状态的操作,取消会移除存储时间戳,使当前状态查询返回Unset。这套四状态查询没有永久的Cancelled状态。界面可以显示历史取消标签,但不应把该标签与当前枚举混为一谈。

单次Unset观察无法区分“从未排程”与“已经排程后又取消”。要证明后者,应保留已接受的排程和取消证据。反过来,取消过的操作后来可能重新排程,所以旧取消事件也不能独自证明其当前状态。

因此,一个有用的界面可以同时显示当前状态和简短历史。例如:“现在为Unset;100时接受排程;105时接受取消。”各部分回答不同问题,彼此不能替代。

返回目录 · 下一节

4. 案例一:Ready的B仍在等待Done的A

从基线排程开始。在110检查B,它的当前状态是Ready。现在先尝试B,还没有执行A。B的前置检查失败,因为A是Ready而不是Done。失败调用使schema仍为1、published仍为false,两个操作都仍为Ready。

接着在110执行A。它通过计时条件,且没有前置操作。给定的目标效果将schema设为2,成功操作成为Done。然后在另一笔、顺序更后的调用中执行B,其时间同样标为110。B现在看到A为Done,自身计时条件也满足,schema 2又满足目标条件。B成功发布并成为Done。

最终目标是schema 2且published为true,两个操作状态均为Done。相同时间戳不表示同时发生:练习明确规定成功的A调用排在成功的B调用之前。反转该顺序,就会出现前面那次失败。

这个案例展示了三个不同条件:B的等待时间已结束;准确的前置操作已完成;目标调用成功。Ready查询只回答其中第一个问题,不能保证提交的调用被纳入、有人执行,或更大的组织流程已完成。

应把失败的B调用保留为一次没有状态效果的尝试,而不是从历史中删除,或误记为成功发布。核对应同时保留已观察到的失败,以及后来使成功成为可能的状态变化。

返回目录 · 下一节

5. 案例二:取消A不会取消B

重置到基线,在105取消A。此刻A是Waiting,因此给定的获授权取消成功,A的已存计时值被移除。B记录的就绪时间仍为110。

到110时,A是Unset,B是Ready。目标仍为schema 1且未发布。B不能执行,因为它指定的前置操作A不是Done。给定的取消规则不会把删除动作级联传播到B的队列项。

因此,两种常见概括都是错的。“A被取消,所以整个队列都取消了”虚构了级联效果;“B已经Ready,所以取消A不再重要”忽略了前置检查。准确状态是:后续操作仍在队列中,时间条件已满足,但指定依赖尚未满足。

当前查询不能告诉我们A为什么是Unset。本例中,已接受的取消历史提供了这个解释。如果缺少历史,仍可以根据A不是Done诊断当前依赖失败,但不能有依据地叙述此前发生过取消。

还要注意,单纯多等一段时间不能修复失败。在这段有界历史中,没有转换完成A。更多时间流逝不会把Unset变成Done。要改变依赖结果,需要一次相关的成功动作,而不是再读一次时钟。

返回目录 · 下一节

6. 案例三:同一个A带着新计时值回来

接着案例二继续。在112,以延迟10重新排程完全相同的A。它此前是Unset,因此新排程被接受,并记录就绪时间122。B保留原来的110,没有自动重启计时。

到121时,B是Ready,A是Waiting。尝试B仍因前置条件失败。到122时,A为Ready,在给定假设下可以执行,产生schema 2并成为Done。到123时,B成功执行并发布目录。

最终目标结果与案例一相同,但历史和计时值不同。A原来的110被取消操作移除了;当前有效计时来自112时接受的新排程。B的计时值则一直不变。

重新排程不会在A的同一行追加第二个同时有效的计时器。在这种源码风格模型中,一行保存的是当前时间戳或标记。旧排程仍是相关历史证据,但在取消与重新排程之后,不再决定当前的就绪条件。

A成为Done后,在124尝试取消它,再尝试重新排程相同A。两次尝试都失败,Done标记保留。已完成操作不是可取消的待处理操作,也不是可排程的Unset操作。不要把“取消后可以复用ID”的规则推广到已经完成的ID。

返回目录 · 下一节

7. 案例四:A2的相同效果不能满足A的身份要求

再次重置。在105取消A,然后在105以延迟10排程A2。A2执行相同的准备效果,也没有前置操作,但salt不同,因此具有设定中不同的操作身份。它的就绪时间是115。

在115执行A2。目录现在有schema 2,A2为Done,A仍为Unset,B仍为Ready。此时尝试B,仍会失败,因为B指定的前置操作是A,不是A2。

这是一个很有用的核对测试,因为业务字段看起来已经正确。schema 2确实存在,因此目标的schema条件并非障碍。未满足的是队列中准确前置操作的完成记录。观察目标状态,不能替代这条记录。

不能悄悄把B的前置列改成A2。这样描述的是另一个完整操作元组,也就是另一个后续操作身份。旧的排队B不会因为操作者换了一种更方便的“准备完成”解释而自行改变。

这个结果也不表示所有治理设计都禁止同效果替代。它说明的是准确前置ID设计的含义。其他应用可以规定不同的依赖规则,但相应证据和检查器必须明确遵守那些规则。

返回目录 · 下一节

8. 案例五与六:取消方向与准确相等边界

另起一例,在110取消B,不改动A。B为Ready且仍属于待处理状态,因此取消成功,使B成为Unset。随后在110执行A。A变为Done,schema变为2,但published仍为false。在111尝试B,会因B不是Ready而失败。

取消后续操作不会取消前置操作;完成前置操作,也不会自动执行或重新排程后续操作。这些是独立的队列转换。依赖检查的方向,不应被误认为自动传播机制。

再次重置,在105尝试重复排程仍有原始有效排程的A。重复排程失败,原来的就绪时间仍为110。失败尝试不会把它替换成115。

在109执行会因时间条件失败,在准确的110执行则通过时间条件,前提是其他给定条件也成立。该设定的Ready规则包含相等边界。这些测试能防止差一个时间单位的错误,改变本来正确的队列解释。

完整练习记录了八项已检查结果:除了最初的有序调用、取消前置操作、不同salt替代、取消后续操作、重复排程与边界案例,以及下文的未来最短延迟案例外,还把同ID重新排程和Done ID拒绝分别记录。

返回目录 · 下一节

9. 案例七:新最短延迟约束未来排程

使用另一次基线重置。给定证据说明,在105的新排程尝试之前,一次有效的最短延迟更新已经完成,把最短延迟改为20。这是练习接受的输入,不是让读者执行的不受限制配置操作。

A和B保留已记录的110就绪时间。新的最短延迟不会重写现有时间戳。在105,以延迟10新排程A2会失败;以延迟20排程则成功,并记录就绪时间125。

因此在110,A和B为Ready,A2为Waiting。当前最短延迟为20,不表示每个已有操作都必须从旧排程时刻重新等满20。判断某个操作的当前计时,需要检查已接受的排程结果,以及之后是否取消或重新排程。

固定版本实现要求延迟更新由时间锁自身调用。本文直接给定已接受的更新证据,不构造其治理路径,以便把队列任务与此前的批准和管理权限课程分开。

返回目录 · 下一节

10. 按可靠顺序重建记录表

首先,为这段有界历史建立完整起始快照:操作身份、目标状态、相关计时值、最短延迟,以及这些记录适用的上下文。标明后续哪些记录是已接受的转换,哪些只是失败尝试。提交请求本身不等于已接受的排程或执行。

其次,按给定顺序处理已接受的转换。排程Unset ID会赋予就绪时间;取消待处理ID会移除它;成功执行会记录完成,并应用规定的目标效果。失败调用不改变模型。即使当前时间戳项消失,也应保留历史记录。

第三,在要求的观察时间计算每个查询状态。之后才评估执行问题:本操作是否Ready?准确的非零前置操作是否Done?调用者是否获授权?给定目标条件是否通过?

最后,分别报告当前队列状态、依赖满足情况和目标结果。在A2案例中,三者分别是:B为Ready,A依赖未满足,schema 2已存在但仍未发布。把它们压成一个“成功”标签,会丢掉最重要的发现。

返回目录 · 下一节

11. 可复现性与缺失证据

本地检查器为每个符号操作保存固定前置操作和效果,并使用一个存储时间戳映射。它重跑全部八项独立结果及明确说明的续接。第二个检查器则分别保存待处理记录和已完成ID集合,以减少对同一种表示方式的依赖。

这些检查验证的是给定模型中的算术和状态转换。它们不会认证真实事件流,不计算实际操作哈希,不执行目标字节码,也不证明实际部署的配置。如果来源记录缺少网络上下文、遗漏取消或隐藏自定义覆写,模型重放成功也无法填补该证据缺口。

活性是尤其重要的边界。一个有效、Ready且前置已完成的操作,可能因为所需调用始终没有发生而一直未执行。工作表识别的是给定事实下允许的转换,不承诺有人提交,也不承诺网络纳入。

同样,完成标记证明的是模型规定的成功操作,不证明所有外部界面都采用了结果,也不证明真实世界的组织目标已经实现。更广泛的结果检查应放在这项狭义队列重建旁边,而不是被偷偷包含在里面。

返回目录 · 下一节

12. 练习与参考答案

  1. 在110,A和B都为Ready。为什么先尝试B会失败?
  2. A在105被取消。单看Unset查询能证明这段历史吗?
  3. 相同A在112以延迟10重新排程。两个当前有效就绪时间分别是多少?
  4. A2为Done且schema为2,为什么B仍可能失败?
  5. 失败的重复排程能替换A已有计时值吗?
  6. 最短延迟改为20,会改变现有A和B的时间戳吗?

答案:1. B要求A为Done,而不只是Ready。2. 不能;从未排程的ID也可能是Unset,需要取消历史证据。3. A在122就绪,B保留110。4. B指定的是A,而A的完成记录仍不存在。5. 不能,失败调用保留原计时值。6. 不会;在给定案例中,它约束新的排程尝试。

最后做一个延伸:比较取消后的Unset A,以及执行后的Done A。模型允许哪一个重新排程?只有Unset A。请用当前排程前提解释,而不是泛泛地说某件事“已经过去很久”。

返回目录 · 下一节

来源与后续阅读

主要实现于2026年10月12日核对:OpenZeppelin TimelockController v5.4.0。源码对照支持本文使用的身份、状态查询、依赖和排程边界;较小时间值、目录效果和符号历史均为虚构教学数据。

如果问题是谁批准了变更,以及队列之外是否真正采用了结果,可继续阅读治理权限与实际采用。如果有人把投票通过误当成操作完成,可阅读投票证据与执行。

返回目录