SignedMsgType
SignedMsgType 是一个单字节值,用于表示被签名消息的类型。
其在 Go 中定义如下:
Timestamp
时间戳校验较为微妙,目前对提案或投票中包含的时间戳没有设置边界限制。 通常预期验证者会诚实地报告其本地时钟时间。 提交中所有时间戳的中位数会被用作下一个区块高度的时间戳。 对于同一个验证者,时间戳应当严格单调递增,不过当前并未强制执行这一点。ChainID
ChainID 是一个非结构化字符串,最大长度为 50 字节。 未来 ChainID 可能会变成结构化形式,也可能支持更长长度。 目前建议签名器为特定的 ChainID 进行配置,并且只对与该 ChainID 对应的投票和提案进行签名。BlockID
BlockID 是用于表示区块的结构:nil 区块,要么表示一个完整区块。
为此,我们分别引入 BlockID.IsZero() 和 BlockID.IsComplete() 两个方法。
对于 BlockID b,如果以下各项都为真,则 BlockID.IsZero() 返回 true:
b,如果以下各项都为真,则 BlockID.IsComplete() 返回 true:
Proposals
用于签名的提案结构如下:p,以下每一行都求值为 true,则该提案有效:
Votes
用于签名的投票结构如下:v,以下每一行都求值为 true,则该投票有效:
Invalid Votes and Proposals
不满足上述规则的投票和提案会被视为无效。 传播无效投票和提案的对等节点,可能会被网络中的其他对等节点断开连接。 不过请注意,目前并没有显式机制去惩罚那些签署了不满足这些基础校验规则的投票或提案的验证者。Double Signing
签名器必须谨慎避免对互相冲突的消息进行签名,这也称为“双重签名”或“作恶性重复表态(equivocating)”。 CometBFT 提供了发布验证者签署冲突投票证据的机制,因此应用层可以对其进行惩罚。 需要注意的是,CometBFT 当前尚不处理冲突提案的证据,不过未来可能会支持。State
为了防止这类双重签名,签名器必须跟踪最近一次已签名消息的高度、轮次和类型。 假设签名器保存以下状态s:
m 签名后,签名器设置:
Proposals
仅当以下任一条件为真时,签名器才应对提案p 进行签名:
Votes
仅当以下任一条件为真时,签名器才应对投票v 进行签名:
- 高度更高
- 在相同高度下轮次更高
- 在相同高度和轮次下,是一个 prevote,且我们尚未签署 prevote 或 precommit(但已签署 proposal)
- 在相同高度和轮次下,是一个 precommit,且我们尚未签署 precommit(但已签署 proposal 和/或 prevote)
nil 的投票,也就是 BlockID.IsZero() 为 true 的情况。
如果签名器已经签署过一个 BlockID.IsZero() 为 true 的投票,那么它就不能再对相同高度、轮次和类型的另一个 BlockID.IsComplete() 为 true 的投票进行签名。
因此,在相同高度和轮次下,某一特定类型的投票(即 0x01 或 0x02)只能签署一次。
Other Rules
根据 CometBFT 所采用的 Tendermint 共识算法规则,一旦验证者对某个区块进行了 precommit,它就会被“锁定”在该区块上,这意味着除非它看到充分的理由(即来自更高轮次的 polka),否则不能再对另一个区块进行 prevote。 更多细节请参见共识规范。 违反这条规则被称为“amnesia”。 与容易检测的 equivocation 不同,amnesia 很难被检测,除非能够获得所有验证者的投票,因为这正是“解锁”所需理由的组成部分。 因此,amnesia 不会在协议内受到惩罚,而且签名器也无法轻易阻止它发生。 如果足够多的验证者同时发起 amnesia 攻击,它们可能导致区块链分叉;此时必须启用链下协议来收集所有验证者的投票,并确定是谁存在不当行为。 更多细节请参见分叉检测。Here we specify the rules for validating a proposal and vote before signing. First we include some general notes on validating data structures common to both types. We then provide specific validation rules for each. Finally, we include validation rules to prevent double-sigining.
SignedMsgType
TheSignedMsgType is a single byte that refers to the type of the message
being signed. It is defined in Go as follows:
Timestamp
Timestamp validation is subtle and there are currently no bounds placed on the timestamp included in a proposal or vote. It is expected that validators will honestly report their local clock time. The median of all timestamps included in a commit is used as the timestamp for the next block height. Timestamps are expected to be strictly monotonic for a given validator, though this is not currently enforced.ChainID
ChainID is an unstructured string with a max length of 50-bytes. In the future, the ChainID may become structured, and may take on longer lengths. For now, it is recommended that signers be configured for a particular ChainID, and to only sign votes and proposals corresponding to that ChainID.BlockID
BlockID is the structure used to represent the block:nil block, or a complete one.
We introduce two methods, BlockID.IsZero() and BlockID.IsComplete() for these cases, respectively.
BlockID.IsZero() returns true for BlockID b if each of the following
are true:
BlockID.IsComplete() returns true for BlockID b if each of the following
are true:
Proposals
The structure of a proposal for signing looks like:p:
Votes
The structure of a vote for signing looks like:v:
Invalid Votes and Proposals
Votes and proposals which do not satisfy the above rules are considered invalid. Peers gossipping invalid votes and proposals may be disconnected from other peers on the network. Note, however, that there is not currently any explicit mechanism to punish validators signing votes or proposals that fail these basic validation rules.Double Signing
Signers must be careful not to sign conflicting messages, also known as “double signing” or “equivocating”. CometBFT has mechanisms to publish evidence of validators that signed conflicting votes, so they can be punished by the application. Note CometBFT does not currently handle evidence of conflciting proposals, though it may in the future.State
To prevent such double signing, signers must track the height, round, and type of the last message signed. Assume the signer keeps the following state,s:
m, the signer sets:
Proposals
A signer should only sign a proposalp if any of the following lines are true:
Votes
A signer should only sign a votev if any of the following lines are true:
- at a higher height
- at a higher round for the same height
- a prevote for the same height and round where we haven’t signed a prevote or precommit (but have signed a proposal)
- a precommit for the same height and round where we haven’t signed a precommit (but have signed a proposal and/or a prevote)
nil, ie. where BlockID.IsZero() is true. If a
signer has already signed a vote where BlockID.IsZero() is true, it cannot
sign another vote with the same type for the same height and round where
BlockID.IsComplete() is true. Thus only a single vote of a particular type
(ie. 0x01 or 0x02) can be signed for the same height and round.