☰
Tendermint 提议者时间戳(Proposer-Based Timestamps):BFT 时间机制的算法级重构
2026/10/12 3:03:25 网站建设 项目流程
  • 区块链
  • 共识算法

【免费下载链接】tendermint

⟁ Tendermint Core (BFT Consensus) in Go

项目地址:https://gitcode.com/gh_mirrors/te/tendermint
点击查看免费下载

导读

本文聚焦 Tendermint 共识中区块时间的产生方式,系统讲解一份以"提议者时间戳(Proposer-Based Time,简称 PBT)"替代传统"中位数 BFT 时间(bfttime)"的算法级重构方案。原文来自本仓库 spec/consensus/proposer-based-timestamp 目录下的三篇配套规范(主文档 pbts_001_draft.md、系统模型 pbts-sysmodel_001_draft.md 与算法草案 pbts-algorithm_001_draft.md),并辅以仓库内 TLA+ 形式化规范 与 Go 共识实现源码进行印证。读完本文,你将掌握:PBT 协议如何将时间从"投票携带"改为"提议携带"、PRECISION/MSGDELAY/ACCURACY三个系统参数如何支撑安全性与活性、共识算法伪代码逐条改写为带时间的版本,以及这套方案在现实实现(如 consensus/state.go 的 propose 步骤与 config/config.go 的超时配置)中对应的形态。

背景:为什么需要重构 BFT 时间

现有 bfttime 的工作方式

在 spec/consensus/bft-time.md 中记录了当前 Tendermint 的时间机制(以下称"旧方案" bfttime):

  • 验证者在PRECOMMIT消息中携带自己的本地时间;
  • 提议者收集到2f+1个PRECOMMIT后,以"按投票权加权的投票时间中位数"作为下一个区块头Time字段的值。

仓库中的实现佐证了这一机制:加权中位数由 types/time/time.go 的WeightedTime与WeightedMedian计算(median := totalVotingPower / 2,再按时间排序、按权重累减定位中位数),而PRECOMMIT投票的时间则通过max(lockedBlock/Proposal.Timestamp + 1ms, time.Now())之类的规则确定(见 spec/consensus/bft-time.md)。时间字段是int64类型的 Unix 毫秒时间戳。

旧方案满足两个核心性质:时间单调性(相邻高度H1.Time < H2.Time)与时间有效性(区块时间只由正确进程的PRECOMMIT时间界定,恶意进程无法任意抬高时间)。

旧方案的六项分析结论

主文档 对 bfttime 给出了六点剖析:

  1. 容错性:只要故障验证者的投票权少于 1/3,计算出的中位数时间必然落在正确验证者发送时间的区间内,因此是拜占庭容错的。
  2. 故障验证者的影响力:当故障方掌握超过 1/2(即介于 1/3 与 2/3 之间)的投票权时,时间完全被故障方掌控,这对轻客户端安全尤其不利。
  3. 提议者对区块时间的影响力:提议者计算中位数时可从2f+1票中任意挑选子集、可任意选择用于计算的轮次,因此对bfttime有相当的自由度。
  4. 活性:协议活性不依赖时钟同步,但依赖有界的消息延迟。
  5. 与真实时间的关系:没有时钟同步意味着计算出的区块时间与真实时间没有任何保证的关联。
  6. 聚合签名:PRECOMMIT消息携带各不相同的时间字段,阻碍了聚合签名的使用。

提议者时间戳方案(PBT)的改进

PBT 的核心思路是"把时间从投票移到提议":不再是每个验证者在PRECOMMIT里各报一个时间,而是提议者在PROPOSE消息中携带自己的时间now_p,验证者用本地时钟校验这个时间是否"及时(timely)"。新旧方案逐项对比见下表(摘自 主文档):

维度bfttime(旧)PBT(新)
容错性保持保持
故障方对时间的控制投票权 > 1/2 即可完全控制仅当故障方 > 2/3 时才能破坏,极端情况才可能
提议者对时间自由度可任意挑选子集/轮次在<1/3故障假设下,第一类自由度被消除;轮次选择仍存在
活性不依赖时钟同步依赖新增的时钟同步假设,仍依赖消息延迟(不可避免)
与真实时间的关系无保证形式化时钟同步后,区块时间与真实时间有明确定义的关系
聚合签名被时间字段阻碍PRECOMMIT不再携带时间,允许聚合签名

需要说明:PBT 引入了"验证者时钟同步"这一额外假设(下文PRECISION/ACCURACY),这是它与旧方案最本质的系统模型差异。此外 主文档 还记录了一个与实现更贴近的语义调整:接收步骤不再丢弃超时的 PROPOSE 消息,而是保留全部消息、只对及时者打上timely标记。

系统模型:三个时间参数与安全性质

Part I 系统模型文档 为算法改写给出了精确的形式化基础。

时钟与消息延迟假设

  • [PBTS-CLOCK-NEWTON.0]:存在一个牛顿参考真实时间t(UTC)。
  • [PBTS-CLOCK-PRECISION.0]:存在系统参数PRECISION,使得任意两个正确验证者V、W在同一真实时刻的本地时钟差满足|C_V(t) - C_W(t)| < PRECISION。
  • [PBTS-MSG-D.0] / [PBTS-MSG-FAIR.0]:存在以本地时钟时间计量的系统参数MSGDELAY,正确提议者到正确验证者的PROPOSE消息端到端延迟小于MSGDELAY。注意文档特别指出:该假设同时约束了消息延迟与时钟([PBTS-MSG-D.0] 的备注)。
  • [PBTS-CLOCK-GROW.0]:一次共识实例期间,本地时钟不会被回拨,即每个正确验证者在高度k上满足beginConsensus(V,k) < endConsensus(V,k)。

文档强调(见 pbts_001_draft.md):PRECISION与MSGDELAY会出现在代码层面;而ACCURACY不必在代码层面可见,甚至可以看作随时间变化的量——它在一次共识实例中越小,区块时间就越贴近真实时间。

决策相关的不变量

  • [PBTS-PROPOSE.0]:提议者提议一对值(v, t)。
  • [PBTS-INV-AGREEMENT.0](Agreement):没有两个正确验证者会就不同的值v达成决策。
  • [PBTS-INV-TIME-VAL.0](Time-Validity):即使多达2f个验证者故障,正确验证者决定的时间t也是"OK"的。
  • [PBTS-INV-TIME-AGR.0](Time-Agreement):同一轮中决策的两个正确验证者必然决定相同的t。
  • [PBTS-DECISION-ROUND.0]:由于验证者可能在不同轮次决策,下一区块的提议者会选择某一轮的 commit(至少2f+1个PRECOMMIT)作为"canonic"决策轮,从而隐式地在决策轮对应的时间中做选择——这与旧方案中基于中位数的选择在本质上是相同的自由度;文档指出,由于 Cosmos hub 上大多数共识实例单轮即终止,这一自由度在实践中几乎观察不到。

共识层安全(Consensus-Time-Validity)

[PBTS-CONSENSUS-TIME-VALID.0]给出了区块时间与共识起止时刻的界:设beginConsensus(k)为所有正确验证者进入高度k的最小本地时刻、endConsensus(k)为最大离开时刻,则被有效 commit 签名、且包含至少一个正确验证者PRECOMMIT的区块时间b.time满足

beginConsensus(k) - PRECISION <= b.time < endConsensus(k) + PRECISION + MSGDELAY

该性质的分析场景是提议者故障(不计入上述区间),并估计正确验证者收到并"接受"提议消息的时刻;当提议者正确时,可进一步得到[PBTS-CONSENSUS-SAFE-VALID-CORR.PROP.0]:beginConsensus_k <= b.time <= last-beginConsensus_k,即区块时间落在正确验证者进入该高度的本地时间区间内。这两条性质成立的前提是:第 1 轮提议者正确、[TMBC-FM-2THIRDS.0](多于 2/3 正确)成立、[PBTS-MSG-FAIR.0]、[PBTS-CLOCK-PRECISION.0] 与 [PBTS-CLOCK-GROW.0] 成立(文档对后者标注了 TODO:是否充分待确认)。

真实时间安全(Real-Time Safety)

要建立区块时间与真实时间的关系,需要引入[PBTS-CLOCKSYNC-EXTERNAL.0]:存在系统参数ACCURACY,使所有真实时刻t、所有正确验证者V满足|C_V(t) - t| < ACCURACY。在此基础上:

  • [PBTS-CONSENSUS-REALTIME-VALID.0](提议者可能故障):设propRecvTime(m)为首个正确验证者收到触发PRECOMMIT的提议消息m的真实时刻,则propRecvTime(m) - ACCURACY - PRECISION < b.time < propRecvTime(m) + ACCURACY + PRECISION + MSGDELAY;
  • [PBTS-CONSENSUS-REALTIME-VALID-CORR.0](提议者正确):设proposalTime(m)为提议者真实发送m的时刻,则proposalTime(m) - ACCURACY < b.time < proposalTime(m) + ACCURACY(因算法在发送时刻令m.time <- now_p)。

活性(Liveness)

当 [TMBC-FM-2THIRDS.0]、[PBTS-MSG-FAIR.0]、[PBTS-CLOCK.0]、[PBTS-CLOCK-GROW.0](同样标注 TODO)成立时,最终会存在高度k的有效 commit;更进一步,[PBTS-CONSENSUS-LIVE-VALID-CORR.PROP.0]指出:若第 1 轮提议者正确,所有正确验证者将在有界时间内于第 1 轮完成决策。

这些性质在仓库的 TLA+ 规范 中被逐一实现为不变量:AgreementOnValue、AgreementOnTime、ConsensusTimeValid、ConsensusSafeValidCorrProp、ConsensusRealTimeValid、ConsensusRealTimeValidCorr、BoundedDelay,以及活性条件ConsensusTimeLive。该 TLA+ 模块将PRECISION、ACCURACY、Delay等声明为常量,并将Proposal(v, t)、Decision(v, t, r)建模为时间与值的元组。

算法改写:从 arXiv 伪代码到带时间的规则

Part II 算法文档 的总体策略是:显式区分消息接收步骤与处理步骤(原 arXiv 论文只隐式地"评估收到的消息规则"),并保持 arXiv 论文中除下述规则外的一切不变。PROPOSE消息的proposal字段被扩展为对(v, time),即提议的共识值v与提议时间time。

接收步骤:及时性判定

[PBTS-RECEPTION-STEP.0]:进程p在本地时刻now_p收到消息m时:

  • 若m为PROPOSE类型且满足now_p - PRECISION < m.time < now_p + PRECISION + MSGDELAY,则标记为timely;
  • 否则标记为untimely。

注意这是双边界判定:时间既不能太旧(低于now_p - PRECISION),也不能太新(超过now_p + PRECISION + MSGDELAY,多出的MSGDELAY是给在途提议的传输预算)。在 TLA+ 中,对应ReceiveProposal(p)动作,其判定条件localClock[p] - Precision < t ∧ t < localClock[p] + Precision + Delay与文档逐字对应(见 TendermintPBT_001_draft.tla 中标注[PBTS-RECEPTION-STEP.0]的注释)。

新的 StartRound:单调时间等待 + 时间上链

[PBTS-ALG-STARTROUND.0]对原StartRound有两处新增:

  1. 若提议者本地时间不大于上一个区块时间,则等待直到now_p > blockTime(保证区块时间严格单调递增);
  2. 提议者将其当前时间now_p作为提议的一部分发出。
function StartRound(round) { blockTime ← block time of block h_p - 1 waitingTime ← blockTime + 2 * ACCURACY + MSGDELAY - now_p round_p ← round step_p ← propose if proposer(h_p, round_p) = p { wait until now_p > blockTime // new wait condition if validValue_p != nil { proposal ← (validValue_p, now_p) // added "now_p" } else { proposal ← (getValue(), now_p) // added "now_p" } broadcast ⟨PROPOSAL, h_p, round_p, proposal, validRound_p⟩ } else { schedule OnTimeoutPropose(h_p,round_p) to be executed after max(timeoutPropose(round_p), waitingTime) } }

同时文档给出了PROPOSE步骤超时上界更新的推导链:

  • 正确的提议者最迟在真实时间blockTime + ACCURACY发出(此时其本地时钟必然已超过blockTime);
  • 接收者在blockTime + ACCURACY + MSGDELAY前收到PROPOSE;
  • 接收者本地时钟此时<= blockTime + 2 * ACCURACY + MSGDELAY;
  • 因此接收者p进入该轮时可把等待时间设为waitingTime => blockTime + 2 * ACCURACY + MSGDELAY - now_p。

最终该轮超时应取max(timeoutPropose(round_p), waitingTime)。文档还预留了未来演进:若引入区块延迟参数BLOCKDELAY,提议者应等待now_p > blockTime + BLOCKDELAY再发PROPOSE,且waitingTime中也要加上BLOCKDELAY。

这一"等待 + 超时放大"的语义与仓库实现结构可相互印证:Go 实现中enterPropose通过cs.scheduleTimeout(cs.config.Propose(round), ...)调度timeoutPropose(consensus/state.go),而超时值由 config/config.go 的ConsensusConfig.Propose(round)计算:TimeoutPropose + TimeoutProposeDelta * round(默认timeout_propose = 3000ms、timeout_propose_delta = 500ms,见 config/config.go 与 Propose 方法)。PBT 方案中的waitingTime相当于在既有超时之上叠加与blockTime、时钟精度相关的分量——当前仓库实现尚未引入该叠加(这是规范草案相对实现的超前设计)。

规则改写一:第 22-27 行(首次收到及时提议 → 预投票)

原规则是"对值v预投票",新规则变为"对值v和时间t预投票":

upon timely(⟨PROPOSAL, h_p, round_p, (v,t), −1⟩) from proposer(h_p, round_p) while step_p = propose do { if valid(v) ∧ (lockedRound_p = −1 ∨ lockedValue_p = v) { broadcast ⟨PREVOTE, h_p, round_p, id(v,t)⟩ } else { broadcast ⟨PREVOTE, h_p, round_p, nil⟩ } step_p ← prevote }

关键变化在于PREVOTE的投票对象由id(v)变为id(v,t),即把提议时间纳入投票哈希。TLA+ 中对应UponProposalInPropose(p),其中mid := IF IsValid(v) ∧ (lockedRound[p] = NilRound ∨ lockedValue[p] = v) THEN Id(Proposal(v, t)) ELSE NilProposal。

规则改写二:第 28-33 行(旧轮次预投票 + 新提议)

当第 1 轮未达成共识时,后续轮次的提议者可能提议相同的值但不同的时间,因此旧PREVOTE中的时间tvote不必与当前PROPOSAL的时间tprop一致。[PBTS-ALG-OLD-PREVOTE.0]规定:只要值v匹配,验证者就可以为当前轮次发PREVOTE:

upon timely(⟨PROPOSAL, h_p, round_p, (v, tprop), vr⟩) from proposer(h_p, round_p) AND 2f + 1 ⟨PREVOTE, h_p, vr, id((v, tvote)⟩ while step_p = propose ∧ (vr ≥ 0 ∧ vr < round_p) do { if valid(v) ∧ (lockedRound_p ≤ vr ∨ lockedValue_p = v) { broadcast ⟨PREVOTE, h_p, roundp, id(v, tprop)⟩ } else { broadcast ⟨PREVOTE, hp, roundp, nil⟩ } step_p ← prevote }

这里需要消化"新旧时间错位":tvote属于历史轮次vr,而新PREVOTE投票用id(v, tprop)。TLA+ 中UponProposalInProposeAndPrevote(p)将msgsPrevote[vr]中id = Id(Proposal(v, t2))的消息计数并对照THRESHOLD2 == 2*T+1,随后广播Id(Proposal(v, t1))。

规则改写三:第 36-43 行(新轮次预投票达成 +2/3)

[PBTS-ALG-NEW-PREVOTE.0]:当及时提议(v,t)与当前轮2f+1个id(v,t)预投票同时满足时,第一次进入此状态的处理如下:

upon timely(⟨PROPOSAL, h_p, round_p, (v,t), ∗⟩) from proposer(h_p, round_p) AND 2f + 1 ⟨PREVOTE, h_p, round_p, id(v,t)⟩ while valid(v) ∧ step_p ≥ prevote for the first time do { if step_p = prevote { lockedValue_p ← v lockedRound_p ← round_p broadcast ⟨PRECOMMIT, h_p, round_p, id(v,t))⟩ step_p ← precommit } validValue_p ← v validRound_p ← round_p }

注意:lockedValue/validValue等存储值不含时间,时间只存在于消息与决策中——这是本规则与 [PBTS-ALG-UPON-PROP.0] 共同的要点。TLA+ 中UponProposalInPrevoteOrCommitAndPrevote(p)要求step[p] ∈ {"PREVOTE","PRECOMMIT"}并对照Cardinality(PV) >= THRESHOLD2。

规则改写四:第 49-54 行(决策)

[PBTS-ALG-DECIDE.0]:决策同时针对值v与提议消息中的时间t,且不要求提议是 timely 的:

upon ⟨PROPOSAL, h_p, r, (v,t), ∗⟩ from proposer(h_p, r) AND 2f + 1 ⟨PRECOMMIT, h_p, r, id(v,t)⟩ while decisionp[h_p] = nil do { if valid(v) { decision_p [h_p] = (v,t) // decide on time too h_p ← h_p + 1 reset lockedRound_p , lockedValue_p, validRound_p and validValue_p to initial values and empty message log StartRound(0) } }

文档特别强调了一个场景:当提议者对某个正确验证者不及时(untimely)时,需要确保"若其他人都决策,该验证者也必须决策"——这正是决策规则不要求timely标记的原因。TLA+ 中UponProposalInPrecommitNoDecision(p)用p ∈ inspectedProposal[r](收到过该轮提议即可)替代timely要求,并把决策建模为Decision(v, t, round[p]),同时记录endConsensus'与step' = "DECIDED"。

其余规则(超时、nil 投票、round catch-up 等)保持不变。

与仓库实现、形式化验证的对照

Go 实现侧:时间如何进入区块

当前仓库 Go 实现仍是 bfttime 形态(PBT 规范属于草案演进方向),但两处关键结构可供对照:

  1. 加权中位数实现:types/time/time.go 定义WeightedTime{Time, Weight}与WeightedMedian,median := totalVotingPower / 2后按Time.UnixNano()升序、按权重累减定位,即为"按投票权加权的中位数"的落地实现;
  2. Propose 步骤与超时:consensus/state.go 的enterPropose通过scheduleTimeout(cs.config.Propose(round), ...)安排timeoutPropose,并在defaultDecideProposal中经createProposalBlock生成包含区块时间的提案;config/config.go 定义timeout_propose系列配置及其随轮次增长的Propose(round)公式。PBT 草案中的waitingTime = blockTime + 2*ACCURACY + MSGDELAY - now_p与max(timeoutPropose, waitingTime)即是对这一超时机制的改造方向。

TLA+ 形式化验证侧:不变量与动作全覆盖

spec/consensus/proposer-based-timestamp/tla/TendermintPBT_001_draft.tla 是本方案的机器可检验形态:

  • 常量Precision、Accuracy、Delay、ClockDrift分别对应规范中的三个系统参数与"时钟漂移"开关;
  • 动作InsertProposal([PBTS-PROPOSE.0],提议(validValue[p] 或 v, localClock[p]))、ReceiveProposal([PBTS-RECEPTION-STEP.0])、UponProposalInPropose([PBTS-ALG-UPON-PROP.0])、UponProposalInProposeAndPrevote([PBTS-ALG-OLD-PREVOTE.0])、UponProposalInPrevoteOrCommitAndPrevote([PBTS-ALG-NEW-PREVOTE.0])、UponProposalInPrecommitNoDecision([PBTS-ALG-DECIDE.0])与文档规则一一对应,且注释中直接标注了文档编号;
  • 收尾的Inv不变量集合把 Part I 中的性质翻译为可模型检验的公式,例如ConsensusRealTimeValid精确复刻了proposalReceivedTime[r] - Accuracy - Precision < t < proposalReceivedTime[r] + Accuracy + Precision + Delay。

这也解释了规范的三段式组织(README):主文档(问题与对比)→ Part I 系统模型与性质 → Part II 协议规范 → TLA+ 规范,形成"需求 → 假设 → 算法 → 验证"的完整链条。

结语:PBT 方案的价值与边界

从仓库规范与源码可以得出以下结论:

  1. 安全与活性的收益:PBT 在保持 Tendermint 原有容错性的同时,将"故障方控制区块时间"的门槛从 1/2 提升到 2/3,并通过 [PBTS-CLOCKSYNC-EXTERNAL.0] 让区块时间与真实时间之间有了可量化的边界(ACCURACY越小越贴近真实时间);
  2. 工程收益:PRECOMMIT不再携带时间,为聚合签名扫清了障碍;同时明确了接收步骤的timely过滤语义,为共识实现提供了可直接编码的判定规则(now_p - PRECISION < m.time < now_p + PRECISION + MSGDELAY);
  3. 代价与边界:PBT 引入了对时钟同步(PRECISION、ACCURACY)的依赖,且waitingTime需要叠加进PROPOSE超时(对应实现中timeout_propose的改造);当前仓库 Go 实现仍以 bfttime 为主,PBT 以"草案 + TLA+"的形态沉淀于 spec/consensus/proposer-based-timestamp,适用于需要形式化验证支撑的协议演进讨论。
  • 区块链
  • 共识算法

【免费下载链接】tendermint

⟁ Tendermint Core (BFT Consensus) in Go

项目地址:https://gitcode.com/gh_mirrors/te/tendermint
点击查看免费下载

相关推荐

上一篇:DS4Windows:为你的PC游戏体验注入新活力
下一篇:Winpilot安全终极指南:这款Windows优化工具是否值得信赖?

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询