CometBFT 提供了一个确定性的、具备拜占庭容错能力的时间来源。 CometBFT 中的时间由区块头的 Time 字段定义。 它满足以下性质:
  • 时间单调性:时间是单调递增的,也就是说,给定高度为 h1 的头 H1 和高度为 h2 = h1 + 1 的头 H2,有 H1.Time < H2.Time。
  • 时间有效性:给定一组构成 block.LastCommit 字段的 Commit 投票,区块头中 Time 字段的有效取值范围仅由正确进程发送的 Precommit 消息(来自 LastCommit 字段)决定,也就是说,故障进程不能任意增大 Time 的值。
在 CometBFT 的上下文中,时间的类型为 int64,表示以毫秒为单位的 UNIX 时间,即自 1970 年 1 月 1 日以来经过的毫秒数。 在定义 CometBFT 所采用的共识算法 Tendermint 需要强制执行的规则,以确保上述性质成立之前,我们先引入如下定义:
  • Commit 的中位数,等于 Vote 消息中 Vote.Time 字段的中位数,其中 Vote.Time 的取值会按进程投票权重进行重复计数。由于投票权并不是均匀分布的(不是一进程一票),一个投票消息实际上等价于聚合了若干张相同的票,其数量等于发送该投票消息的进程所拥有的投票权。
考虑下面的例子:
  • 我们有四个进程 p1、p2、p3 和 p4,它们的投票权分布如下: (p1, 23)、(p2, 27)、(p3, 10) 和 (p4, 10)。总投票权为 70(N = 3f+1,其中 N 是总投票权,f 是故障进程的最大投票权),因此我们假设故障进程最多拥有 23 的投票权。 此外,在某个 LastCommit 字段中我们有如下投票消息(忽略除 Time 字段之外的所有字段):
    • (p1, 100)、(p2, 98)、(p3, 1000)、(p4, 500)。我们假设 p3 和 p4 是故障进程。再假设 block.LastCommit 消息包含了进程 p2、p3 和 p4 的投票。此时中位数的选取方式如下: 值 98 计数 27 次,值 1000 计数 10 次,值 500 也计数 10 次。 因此,中位数将是 98。无论我们选择哪一组总投票权至少为 2f+1 的消息,中位数都一定会落在正确进程发送的值之间。
我们通过以下规则来保证时间单调性和时间有效性:
  • 设 rs 表示某个进程的 RoundState(共识内部状态)。那么 rs.ProposalBlock.Header.Time == median(rs.LastCommit) && rs.Proposal.Timestamp == rs.ProposalBlock.Header.Time。
  • 此外,在创建 vote 消息时,用于确定 vote.Time 字段的规则应满足以下要求:
    • 如果定义了 rs.LockedBlock,则 vote.Time = max(rs.LockedBlock.Timestamp + time.Millisecond, time.Now()),其中 time.Now() 表示本地 Unix 时间(毫秒)
    • 否则,如果定义了 rs.Proposal,则 vote.Time = max(rs.Proposal.Timestamp + time.Millisecond, time.Now())
    • 否则,vote.Time = time.Now()。在这种情况下,vote 是针对 nil 的,因此不会被纳入下一个区块时间戳的计算。

CometBFT provides a deterministic, Byzantine fault-tolerant, source of time. Time in CometBFT is defined with the Time field of the block header. It satisfies the following properties:
  • Time Monotonicity: Time is monotonically increasing, i.e., given a header H1 for height h1 and a header H2 for height h2 = h1 + 1, H1.Time < H2.Time.
  • Time Validity: Given a set of Commit votes that forms the block.LastCommit field, a range of valid values for the Time field of the block header is defined only by
    Precommit messages (from the LastCommit field) sent by correct processes, i.e., a faulty process cannot arbitrarily increase the Time value.
In the context of CometBFT, time is of type int64 and denotes UNIX time in milliseconds, i.e., corresponds to the number of milliseconds since January 1, 1970. Before defining rules that need to be enforced by Tendermint, the consensus algorithm adopted in CometBFT, so the properties above holds, we introduce the following definition:
  • median of a Commit is equal to the median of Vote.Time fields of the Vote messages, where the value of Vote.Time is counted number of times proportional to the process voting power. As the voting power is not uniform (one process one vote), a vote message is actually an aggregator of the same votes whose number is equal to the voting power of the process that has casted the corresponding votes message.
Let’s consider the following example:
  • we have four processes p1, p2, p3 and p4, with the following voting power distribution (p1, 23), (p2, 27), (p3, 10) and (p4, 10). The total voting power is 70 (N = 3f+1, where N is the total voting power, and f is the maximum voting power of the faulty processes), so we assume that the faulty processes have at most 23 of voting power. Furthermore, we have the following vote messages in some LastCommit field (we ignore all fields except Time field):
    • (p1, 100), (p2, 98), (p3, 1000), (p4, 500). We assume that p3 and p4 are faulty processes. Let’s assume that the block.LastCommit message contains votes of processes p2, p3 and p4. Median is then chosen the following way: the value 98 is counted 27 times, the value 1000 is counted 10 times and the value 500 is counted also 10 times. So the median value will be the value 98. No matter what set of messages with at least 2f+1 voting power we choose, the median value will always be between the values sent by correct processes.
We ensure Time Monotonicity and Time Validity properties by the following rules:
  • let rs denotes RoundState (consensus internal state) of some process. Then rs.ProposalBlock.Header.Time == median(rs.LastCommit) && rs.Proposal.Timestamp == rs.ProposalBlock.Header.Time.
  • Furthermore, when creating the vote message, the following rules for determining vote.Time field should hold:
    • if rs.LockedBlock is defined then vote.Time = max(rs.LockedBlock.Timestamp + time.Millisecond, time.Now()), where time.Now() denotes local Unix time in milliseconds
    • else if rs.Proposal is defined then vote.Time = max(rs.Proposal.Timestamp + time.Millisecond,, time.Now()),
    • otherwise, vote.Time = time.Now()). In this case vote is for nil so it is not taken into account for the timestamp of the next block.