# Tendermint 导读｜乌托邦周报 #20

By [无环图](https://paragraph.com/@0x257dfbc2836361d04fc4badcc3645a9bc63abdbf) · 2023-06-02

---

如果说 Cosmos SDK 是 Layer 1 的黄埔军校，那么 Tendermint 就是黄埔军校的奠基石。

自 2017 年问世以来，Cosmos SDK 是当今使用最为广泛的 L1 框架。从自家的 Cosmos Hub，到新晋金融协议 Injective，无不是出自 Cosmos SDK 之手。而 Tendermint，作为 Cosmos SDK 背后的核心模块，更是驱动了所有这些 L1 项目。

今天我们就来解构一下 Tendermint 的底层逻辑。

**本文要点：**

*   什么是 Tendermint
    
*   从头到尾走一遍 Tendermint
    

* * *

什么是 Tendermint
==============

![来源：https://v1.cosmos.network/intro](https://storage.googleapis.com/papyrus_images/5511895c86f382288961f1daa0c74b3bbeb945b02641404a2cde5fd6c41804f5.png)

来源：https://v1.cosmos.network/intro

Tendermint 由 3 部分组成：

1.  网络层：负责节点间的 P2P 通信。
    
2.  共识层：拜占庭容错（BFT）的 PoS 共识机制。
    
3.  应用层接口：暴露给上层应用的接口，所谓 ABCI（Application BlockChain Interface）。
    

其中，网络层和共识层整体上又被称为 Tendermint Core。

Cosmos SDK 做的，其实是在 Tendermint 的基础上，进一步为大家提供了应用层的逻辑模板，包括如何治理、如何质押、干坏事怎么 slash，以及如何跨链通信。

![来源：https://blog.cosmos.network/tendermint-explained-bringing-bft-based-pos-to-the-public-blockchain-domain-f22e274a0fdb](https://storage.googleapis.com/papyrus_images/503adaa39d3846a1b031d43acb294c69c17cf6a533bffdfe30865836efe1d78a.png)

来源：https://blog.cosmos.network/tendermint-explained-bringing-bft-based-pos-to-the-public-blockchain-domain-f22e274a0fdb

总而言之，Tendermint 把 L1 最共性的部分抽了出来，做成公共组件，让大家专心写应用逻辑就好。这就是 Tendermint 的价值所在。

* * *

从头到尾走一遍 Tendermint
==================

理解 Tendermint，目前最好的资料便是官方文档里的这张流程图了。它从用户提交交易开始，到打包入块，到节点间达成共识，到执行交易，改变状态，完整地呈现了 Tendermint 的主线逻辑。

![](https://storage.googleapis.com/papyrus_images/78ac70483ec350b2936968e359361be03687b823f968e45db74b513a83279f8d.png)

不过除此以外，似乎也没有其他补充性资料展开说明其中的逻辑细节。怎么办？我们不妨从头到尾扒代码看看。

### 用户提交 Tx

这一步骤对应流程图的左上区域。

![](https://storage.googleapis.com/papyrus_images/6e23f1b748a397d777e4a67bdb8b5eaa29ba718fff2b3e861f7e96fdc8e11bda.png)

Tendermint 在 Mempool Cache 介入，检查 Cache 中的 Tx 是否合法，合法的话才会将其加入 Mempool。

对应的逻辑在 Mempool 的 Reactor 中。Reactor 结构体用于响应 P2P 网络层的事件，最关键的是 Receive()，即收到其他节点发过来的消息后如何处理。

![](https://storage.googleapis.com/papyrus_images/33efc4cbe969ffc8569effebe439a3280feb4cc38223b89456d59d4fdd36395b.png)

看看 Mempool Reactor 的实现。收到消息后首先反序列化，然后检查每一条 Tx 是否合法。

![](https://storage.googleapis.com/papyrus_images/eb9aebec12b0213df1b2f785c112d336abab0b19ec7f9fb52019a386fbf5f96e.png)

合法性主要看 Tx 的大小是否在配置的范围内，此处也可以加入自定义的检查逻辑。最终如果 Tx 合法，就加入到 Mempool 中。

![](https://storage.googleapis.com/papyrus_images/79550ac5b88e3f4857cb1e49119fbd9bb77c2cb9373dacd5715fcb9d717615a6.png)

### Proposer 将 Tx 打包入块

Mempool 中的 Tx 打包由 CreateProposalBlock() 完成，后者会在 Mempool 中按 Tx 优先级从高到低，获取一组 Tx，放入区块中，并填充区块头。

![](https://storage.googleapis.com/papyrus_images/779c0235253bba16f7678a0fcb934f7eaf530a15eae902c1a58a949ccda6b7a7.png)

![](https://storage.googleapis.com/papyrus_images/af8610596981414b404efba6471c677b091b71035f5cd4b331f0c5f65ef49d56.png)

### 节点间 3 阶段通信，达成共识

![](https://storage.googleapis.com/papyrus_images/d9c1c75a7bbc918a421a0a1dddba6e21d07f5bacda095586878f7692462faa6d.png)

在区块链的语境下，共识机制讲的是所有节点按相同的顺序，执行同样的一组 Tx（状态转移），最终达到相同状态（State）的方法。

Tendermint 的共识机制和 PBFT 很相似，都有 3 个阶段，只不过命名不同。

![来源：https://pmg.csail.mit.edu/papers/osdi99.pdf](https://storage.googleapis.com/papyrus_images/64e545e9a81e06b08e6078ee3de3113ffe02e32ca4a8c0e8bbf0844a8925e42d.png)

来源：https://pmg.csail.mit.edu/papers/osdi99.pdf

**PBFT 的三阶段为 Pre-prepare、Prepare、Commit。**

其中，Pre-prepare 和 Prepare 阶段需要至少 `2f+1`（`f`为拜占庭节点数）的节点投票，才能进入下一阶段，原因是最坏情况下，所有拜占庭节点（`f`个）同时广播假消息，那么合法节点数至少要超过它们（≥`f+1`），收到投票的节点才能判断哪边消息是真，哪边是假。拜占庭节点和合法节点数相加便得到`2f+1`。

而在 Commit 后的 Reply 环节，客户端则只需要`f+1` 个确认来判断是否 OK，因为最坏情况下，所有拜占庭节点（`f`个）同时作恶，只要有 1 个合法节点与其反馈不一，就可以判断有问题。

最后，考虑所有拜占庭节点（`f`个）还可以选择不广播、不回应，因此要让这个机制能运行下去，总的节点数至少得是`2f+1+f=3f+1`。

**在 Tendermint 里，三阶段对应 Pre-vote、Pre-commit、Commit。**

其中，Pre-vote 和 Pre-commit 阶段都需要至少 `2f+1`的节点投票，Commit 后需要至少`f+1` 个确认来开启下一轮。由于 Tendermint 基于 PoS，这里的节点看的不再是节点数量，而是节点的投票权（如按照质押额分配）。此外，由于节点多，公网环境复杂，且谁都可以加入，Tendermint 加入了超时机制，每个阶段只要超过 Δ 时间，就会跳过当前阶段。

![来源：https://blog.cosmos.network/consensus-compare-casper-vs-tendermint-6df154ad56ae](https://storage.googleapis.com/papyrus_images/3982c0d3386f0d977f6dbef7f9b0175fc8c2b888a725a8b5a5d94e3cb8e55895.png)

来源：https://blog.cosmos.network/consensus-compare-casper-vs-tendermint-6df154ad56ae

![来源：https://arxiv.org/pdf/1807.04938.pdf](https://storage.googleapis.com/papyrus_images/db10cca69d812c85d99ae693c3da961a8415ea5d7ffbf38af0a504e86d4733dc.png)

来源：https://arxiv.org/pdf/1807.04938.pdf

实现层面上，可以分步来看。

首先，Proposer 签名并广播 Proposal。

![](https://storage.googleapis.com/papyrus_images/68b4e41df510661a9984500bb0c8fc662a2b20e4bc9010ad31270209c65e119b.png)

其他节点在收到 Proposer 广播的区块后，验证区块的合法性，包括区块的高度、时间戳、关联的上一个区块的 ID 等。

![](https://storage.googleapis.com/papyrus_images/6267251d523ca92f93b52c1762c8eab60d5c16f4944acae592ec6e23e6931c35.png)

若区块没问题，则签名并广播 Pre-vote，其中包含区块的 ID，否则不包含（nil）。

![](https://storage.googleapis.com/papyrus_images/16ec31ae1b7c1df25f2dc0bbccff08a6e61763b99afab8fa88c0f06f3436ee32.png)

投票结构体的定义也可以简单看一眼。

![](https://storage.googleapis.com/papyrus_images/0d5598cb47514e09ebf2117732b916af7e073fa3ac589c0b16909eee08a1a6f5.png)

接下来，等待一段时间，接收其他节点的 Pre-vote，直到收到三分之二节点的 Pre-vote（这种情形又称作 Polka）或超时。

![](https://storage.googleapis.com/papyrus_images/88707c0492ac12302612cfee1d53be443892a9a7bb2e2fe0c0119b78ff588d13.png)

Pre-vote 阶段过后，进入到 Pre-commit 阶段。检查之前有没有收到三分之二的 Pre-vote，有且自己也见着 Pre-vote 对应的区块的话，就签名并广播 Pre-commit，其中包含该区块的 ID，否则不包含（nil）。

![](https://storage.googleapis.com/papyrus_images/485d5f5bac2d5cf62c56ef7c7fed77bf2e47776f2b324f3b487bdc9189850695.png)

接下来，如前一样，进入 Pre-commit 阶段的等待期。这里不再赘述。

![](https://storage.googleapis.com/papyrus_images/59c4bdaced63d2b983cd75f9baf7fbd5c39a99734c12d353d42898430e55bada.png)

最后，终于走到 Commit 阶段，看大家有没有达成一致（Pre-commit 阶段达成 Polka），有的话就 Commit，没有的话从头再来。

![](https://storage.googleapis.com/papyrus_images/edf61f2874623587d936d27319febe0026bdc42e138973f454d4609bd5dc6417.png)

### 执行 Tx

执行 Commit 的区块，包括其中的所有 Tx，并更新状态。

![](https://storage.googleapis.com/papyrus_images/598f870a58c34cd0025c9f2cdbb2204a0f649fe0485e48214c1eb73ea1b201eb.png)

![](https://storage.googleapis.com/papyrus_images/46c383a9774a09eb175918a2152146e54a7b0f93de155558936b518650cb7afe.png)

### 选举下一区块的新 Proposer

Tendermint 的 Leader Election 是基于 Weighted Round-Robin 算法进行的。

![来源：https://medium.com/@ppio/tendermint-introduction-and-analysis-95bc15749921](https://storage.googleapis.com/papyrus_images/889000ae07c6d60318409ef7284638c3533b0cfb82c477734454ac9ac9f1faad.png)

来源：https://medium.com/@ppio/tendermint-introduction-and-analysis-95bc15749921

一轮走完后，先给所有节点增加优先级。然后，选择优先级最高的作为 Proposer，循环往复。

![](https://storage.googleapis.com/papyrus_images/f65884279f7810876ec70f2ec51780b76893d0420426aa551b536c5393bba2af.png)

注意，被选为 Proposer 的节点在下一轮的 Priority 会排到队尾（被减去 VotingPower 之和）。

![](https://storage.googleapis.com/papyrus_images/fc71053b7b40e884744b4489e1c556fc91fd07882dd1d319ab04e690b3e05bc0.png)

值得一提的是，上一轮的 Proposer 节点在下一轮的优先级会排到末尾。狡猾一点的话，他可以选择退出，再重新加入，这样又会按照他的质押额来赋予投票权，从而插到队伍中间。为了尽可能避免这种情况，新加入的节点的优先级会有一个惩罚系数。

![](https://storage.googleapis.com/papyrus_images/9e79b015fc65f2bb6b00cccdd2cba0c5575580d82c3ec8f42fd9e8c93c13aaf7.png)

* * *

以上便是 Tendermint 的全流程玩法了。后面我们将看看，构建在 Tendermint 之上的 Cosmos SDK 是如何对其进行扩展的。

---

*Originally published on [无环图](https://paragraph.com/@0x257dfbc2836361d04fc4badcc3645a9bc63abdbf/tendermint-20)*
