---
title: Tower BFT
---

本设计介绍了Solana的_塔式BFT_算法。 它解决了以下问题。

- 一些分叉可能最终不会被集群的超级多数接受，投票者需要从对这些分叉的投票中恢复过来。
- 许多分叉可能会被不同的投票者投票，而每个投票者可能会看到一组不同的可投票分叉。 被选中的分叉最终应该为集群收敛。
- 基于奖励的投票有相关的风险。 投票者应该有能力配置他们承担多少风险。
- [回滚成本](tower-bft.md#cost-of-rollback)需要是可计算的。 这对于依赖某种可衡量形式的一致性的客户来说很重要。 破坏一致性的成本需要是可计算的，对于老票来说，成本会超线性增加。
- 节点之间的ASIC速度是不同的，攻击者可以采用比集群其他部分快很多的历史证明ASIC。 共识需要抵御利用历史证明ASIC速度差异性的攻击。

为了简明扼要，本设计假设一个有质押的投票者被部署为集群中的单个验证节点。

## 时间

Solana集群通过可验证的延迟函数生成时间源，我们调用[历史证明](../cluster/synchronization.md)。

历史证明用于为所有活跃的领导者创建一个确定性的循环赛时间表。 在任何给定的时间，只有1个领导者可以提出分叉，这可以从账本本身计算出来。 更多细节，请参见[分叉生成](../cluster/fork-generation.md)和[领袖轮换](../cluster/leader-rotation.md)。

## 锁定

锁定的目的是迫使验证节点对特定的分叉承诺机会成本。 锁定是以时隙来衡量的，因此代表了验证节点在打破对一个分叉的承诺之前需要等待的实时强制延迟。

违反锁定时间并在锁定时间内投票给分歧的分叉的验证节点应该受到惩罚。 建议的惩罚措施是，如果能向集群证明在锁定期内同时投票给非下级分叉，则罚没验证节点的股权。

## 算法

这种方法的基本思路是堆叠共识投票和双重锁定。 栈中的每一票都是对一个分叉的确认。 每一个确认的分叉都是它上面的分叉的祖先。 每一票都有一个以时隙为单位的`锁定`，然后验证节点才能提交一个不包含确认的分叉作为祖先的投票。

当一个投票被添加到堆栈中时，堆栈中所有前一个投票的锁定会翻倍(更多内容请参见[Rollback](tower-bft.md#Rollback))。 每一次新的投票，验证节点都会将之前的投票投入到一个不断增加的锁定中。 在32票时，我们可以认为投票处于`最大锁定`的任何锁定等于或高于`1<<32`的投票都会被dequeued\(FIFO\)。 去排队投票是奖励的触发器。 如果一个投票在去排队之前就过期了，那么它和它上面的所有投票都会从投票堆栈中被弹出\(LIFO\)。 验证节点需要从这一点开始重建栈。

### 回滚

在投票被推送到堆栈之前，投票前所有锁定时间低于新投票的票数都会被弹出。 回滚后锁定时间不会翻倍，直到验证节点追上票数的回滚高度。

例如，一个投票栈的状态如下。

| 票数 | 票数时间 | 锁定时间 | 锁定到期时间 |
| --:| ----:| ----:| ------:|
|  4 |    4 |    2 |      6 |
|  3 |    3 |    4 |      7 |
|  2 |    2 |    8 |     10 |
|  1 |    1 |   16 |     17 |

_第5票_是在时间9，结果状态是

| 票数 | 票数时间 | 锁定时间 | 锁定到期时间 |
| --:| ----:| ----:| ------:|
|  5 |    9 |    2 |     11 |
|  2 |    2 |    8 |     10 |
|  1 |    1 |   16 |     17 |

_第6票_在第10时

| 票数 | 票数时间 | 锁定时间 | 锁定到期时间 |
| --:| ----:| ----:| ------:|
|  6 |   10 |    2 |     12 |
|  5 |    9 |    4 |     13 |
|  2 |    2 |    8 |     10 |
|  1 |    1 |   16 |     17 |

在10时，新的票数赶上了之前的票数。 但是_投票2_在10时到期，所以在11时应用_投票7_时，包括_投票2_以上的投票将被弹出。

| 票数 | 票数时间 | 锁定时间 | 锁定到期时间 |
| --:| ----:| ----:| ------:|
|  7 |   11 |    2 |     13 |
|  1 |    1 |   16 |     17 |

第1票的锁定将不会从16票增加，直到堆栈包含5票。

### 罚没和奖励

验证节点如果尽可能频繁地选择群组其他成员选择的分叉，就应该得到奖励。 这与当投票堆栈满了，需要对最老的投票进行排队时产生奖励是一致的。 因此，每一个成功的dequeue都应该产生一个奖励。

### 回滚的成本

回滚_分叉A_的成本被定义为验证节点确认任何不包括_分叉A_为祖先的其他分叉的锁定时间成本。

在**经济终局性**方面，_分叉A_可以计算为_分叉A_及其后裔回滚所带来的所有奖励损失，再加上由于确认_分叉A_的投票被锁定而带来的机会成本。

### 门槛

每个验证节点可以在该验证节点提交分叉之前，独立设置一个集群承诺的阈值。 例如，在票堆索引7处，锁定时间单位为256个。 除非指数7的投票在集群中的承诺度大于50%，否则验证节点可以扣留投票，让0-7的投票失效。 这使得每个验证节点可以独立控制承诺分叉的风险有多大。 以更高的频率承诺分叉，可以让验证节点获得更多的奖励。

### 算法参数

以下参数需要调整：

- 在dequeue发生之前堆栈中的投票数\(32\)。
- 栈中锁定的增长率 (2x/)。
- 开始的默认锁定\(2\)。
- 最小集群承诺的阈值深度，在承诺分叉之前 (8)。
- 最小集群承诺大小在阈值深度 (50%+)。

### 自由选择

"自由选择 "是一种不可强制执行的验证节点动作。 协议没有办法对这些动作进行编码和强制执行，因为每个验证节点都可以修改代码和调整算法。 一个在所有可能的期货上最大化自我回报的验证节点，其行为应该是系统稳定的，局部贪婪选择的结果应该是在所有可能的期货上贪婪选择。 一组从事破坏协议的选择的验证节点应该受到其利益权重的约束而拒绝服务。 验证节点有两种选择出口。

- 一个验证节点可以在虚拟生成中超越之前的验证节点，并提交一个并发的分叉。
- 验证节点可以不投票，观察多个分叉后再投票

在这两种情况下，集群中的验证节点都有几个分叉可以同时选择，即使每个分叉代表不同的高度。 在这两种情况下，协议不可能检测到验证节点的行为是否是故意的。

### 贪婪地选择并发分叉

当评估多个分叉时，每个验证节点应使用以下规则。

1. Forks必须满足_Threshold_规则。
2. 选取能使所有祖先分叉的总集群锁定时间最大化的分叉。
3. 选取集群交易费用最大的分叉。
4. 选取PoH最晚的分叉。

群集交易费是指存入矿池的费用，详见[质押奖励](staking-rewards.md)章节。

## PoH ASIC抗性

投票数和锁定数呈指数级增长，而ASIC的速度是线性增长。 有两种可能的攻击向量涉及更快的ASIC。

### ASIC审查

攻击者产生一个并发的分叉，它的速度超过了之前的领导者，以达到审查的目的。 这个攻击者提出的分叉将与下一个可用的领导者同时出现。 节点要选择这个分叉，必须满足_Greedy Choice_规则。

1. Fork必须对祖先分叉有同等数量的投票。
2. 分叉不能是一个头，以至于导致票数过期。
3. Fork必须有更多的群交易费。

这种攻击就仅限于删掉前面的领导费，和个人交易费。 但它不能停止集群，也不能减少验证节点集，相比并发的分叉。 费用审查仅限于访问费用去领导，而不是验证节点。

### ASIC回滚

攻击者从一个旧区块中生成一个并发分叉，试图回滚集群。 在这种攻击中，并发分叉与已经被投票的分叉竞争。 这种攻击受到锁定的指数增长的限制。

- 1票有2个时隙的锁定。 并发分叉必须至少领先2个时隙，并在1个时隙内产生。 因此需要快2倍的ASIC。
- 2票有4个时隙的锁存。 并发分叉必须至少领先4个时隙，并在2个时隙中产生。 因此需要快2倍的ASIC。
- 3票有8个时隙的锁定。 并发分叉必须至少领先8个时隙，并在3个时隙中生产。 因此需要一个ASIC快2.6倍。
- 10票有1024个时隙的锁定。 1024/10，即快102.4倍的ASIC。
- 20票有2^20个时隙的锁定。 2^20/20，或快52428.8倍的ASIC。
