什么是 CometBFT

CometBFT 是一种软件,用于在多台机器上安全且一致地复制应用程序。这里的“安全”是指,只要以任意方式失效的机器少于 1/3,CometBFT 就能够正常工作。这里的“一致”是指,每一台没有故障的机器都会看到相同的交易日志,并计算出相同的状态。安全且一致的复制是分布式系统中的一个基础问题;它在广泛应用的容错能力中都起着关键作用,从货币系统、选举系统到基础设施编排等,皆是如此。 能够容忍机器以任意方式失效,甚至变得恶意,这种能力被称为拜占庭容错(BFT)。BFT 的理论已经存在数十年,但相关软件实现直到最近才真正流行起来,这在很大程度上要归功于 Bitcoin 和 Ethereum 等“区块链技术”的成功。所谓区块链技术,本质上是在更现代的环境下对 BFT 的重新表述,重点强调点对点网络与密码学认证。这个名字来源于交易被分批打包进区块的方式,其中每个区块都包含前一个区块的密码学哈希,从而形成一条链。 CometBFT 主要由两个核心技术组件构成:区块链共识引擎和通用应用接口。共识引擎基于 Tendermint 共识算法,确保每台机器都以相同顺序记录相同的交易。应用接口称为 Application BlockChain Interface(ABCI),负责将交易交付给应用处理。与其他自带内置状态机的区块链和共识方案不同,这类内置状态机可能是一个增强版键值存储,或者某种特定脚本语言;开发者可以使用 CometBFT 对任意应用进行 BFT 状态机复制,并自由选择最适合自己的编程语言和开发环境。 CometBFT 的设计目标是易于使用、易于理解、性能优异,并能适用于各种分布式应用。

CometBFT 与其他方案

CometBFT 与两类软件大体相似。第一类是分布式键值存储,例如 Zookeeper、etcd 和 Consul,它们使用的是非 BFT 共识。第二类通常被称为“区块链技术”,既包括 Bitcoin 和 Ethereum 这样的加密货币,也包括 Hyperledger Burrow 这类替代性的分布式账本设计。

Zookeeper、etcd、Consul

Zookeeper、etcd 和 Consul 都是在传统的非 BFT 共识算法之上实现的键值存储。Zookeeper 使用一种名为 Zookeeper Atomic Broadcast 的算法,而 etcd 和 Consul 使用 Raft 日志复制算法。一个典型集群通常包含 3 到 5 台机器,并能容忍少于 1/2 机器发生崩溃故障,例如 3 台中的 1 台或 5 台中的 2 台,但哪怕只有 1 个拜占庭故障,也可能危及整个系统。 这些方案都提供了功能丰富但实现略有不同的键值存储,不过总体目标都集中在为分布式系统提供基础服务,例如动态配置、服务发现、锁、领导者选举等。 CometBFT 本质上是类似的软件,但有两个关键区别:
  • 它具备拜占庭容错能力,这意味着它只能容忍少于 1/3 的机器失效,但这些失效可以包括任意行为,包括被入侵和恶意攻击。
  • 它不规定某一种具体应用,例如某个增强版键值存储。相反,它专注于任意状态机复制,因此开发者可以构建适合自己的应用逻辑,从键值存储到加密货币,再到电子投票平台等各种场景。

Bitcoin、Ethereum 等

CometBFT 采用的 Tendermint 共识算法,起源于 Bitcoin、Ethereum 等加密货币的发展脉络,其目标是提供一种比 Bitcoin 工作量证明更高效、更安全的共识算法。在早期,基于 Tendermint 共识的区块链通常内置一种简单货币,用户若要参与共识,必须将一定数量的货币“绑定”为安全保证金;如果行为不当,这笔保证金可能被收回。这也是 Tendermint 共识之所以成为权益证明算法的原因。 此后,CometBFT 已演化为一个通用型区块链共识引擎,能够承载任意应用状态。这意味着它可以作为其他区块链软件共识引擎的即插即用替代方案。因此,你可以直接使用现有的 Ethereum 代码库,无论其采用 Rust、Go 还是 Haskell 编写,并通过 CometBFT 将其作为一个 ABCI 应用运行。事实上,我们已经在 Ethereum 上这样做过。我们也计划对 Bitcoin、ZCash 以及其他各种确定性应用采用同样的方式。 另一个基于 CometBFT 构建的加密货币应用示例是 Cosmos 网络。

其他区块链项目

Fabric 与 CometBFT 的思路相似,但它对状态管理方式有更多预设,并要求所有应用行为都运行在可能很多的 Docker 容器中,它将这些模块称为 chaincode。它使用了 IBM 团队实现的 PBFT,并在此基础上增强了对潜在非确定性 chaincode 的处理能力。这种基于 Docker 的行为模式也可以作为 ABCI 应用在 CometBFT 中实现,不过对 CometBFT 进行扩展以处理非确定性问题,仍然留待未来完成。 Burrow 实现了 Ethereum Virtual Machine 和 Ethereum 的交易机制,并额外提供了名称注册、权限控制、本地合约以及另一套区块链 API。它使用 CometBFT 作为共识引擎,并提供了一种特定的应用状态。

ABCI 概览

应用区块链接口(ABCI) 允许使用任意编程语言编写的应用实现拜占庭容错复制。

背景

到目前为止,所有区块链“技术栈”(例如 Bitcoin)基本都采用单体式设计。也就是说,每一个区块链技术栈都是一个单独的程序,负责处理去中心化账本的所有问题;这包括 P2P 连接、交易的 mempool 广播、最新区块共识、账户余额、图灵完备合约、用户级权限等。 在计算机科学中,采用单体架构通常不是好的实践。它会让代码组件的复用变得困难,而一旦尝试复用,就往往需要为代码库的分叉维护复杂的维护流程。当代码库本身缺乏模块化设计并陷入“意大利面条式代码”时,这个问题会更加明显。 单体式设计的另一个问题在于,它把你限制在该区块链技术栈所使用的语言之中,或者反过来受制于语言本身。以 Ethereum 为例,它支持一个图灵完备的字节码虚拟机,因此你只能使用那些可以编译成该字节码的语言;虽然这个列表正在不断增长,但仍然非常有限。 与之相对,我们的方法是将共识引擎和 P2P 层与特定区块链应用状态的细节解耦。我们通过把应用细节抽象成一个接口来实现这一点,而这个接口被实现为一种 socket 协议。

ABCI 简介

CometBFT 作为“共识引擎”,通过满足 ABCI 的 socket 协议与应用通信,这个协议也就是 CometBFT Socket Protocol。 为了便于理解,我们以广为人知的加密货币 Bitcoin 为例。Bitcoin 是一种加密货币区块链,其中每个节点都维护一个经过完整审计的未花费交易输出(UTXO)数据库。如果你想基于 ABCI 构建一个类似 Bitcoin 的系统,那么 CometBFT 将负责:
  • 在节点之间共享区块和交易
  • 建立交易的规范化、不可变顺序 (即区块链)
应用则负责:
  • 维护 UTXO 数据库
  • 验证交易的密码学签名
  • 防止交易花费不存在的交易输出
  • 允许客户端查询 UTXO 数据库
CometBFT 通过在应用进程与共识进程之间提供一个非常简单的 API,也就是 ABCI,从而能够将区块链设计拆解开来。 ABCI 由 3 类主要消息组成,这些消息会从核心组件传递到应用,应用再返回相应的响应消息。 这些消息的定义见:ABCI 消息类型。 FinalizeBlock 消息是应用侧的核心执行载体。区块链中的每笔交易都会在这个消息中完成最终处理。应用需要根据当前状态、应用协议以及交易的密码学凭证,对通过 FinalizeBlock 接收到的每笔交易进行验证。FinalizeBlock 只负责准备将要执行的更新,并不会改变应用状态。真正的状态变更会在稍后的阶段提交,也就是 commit 阶段。 CheckTx 消息用于验证交易。CometBFT 的 mempool 会先通过 CheckTx 检查交易有效性,并且只会将有效交易转发给其对等节点。例如,应用可以检查交易中的递增序列号;如果序列号过旧,就在 CheckTx 阶段返回错误。另一种做法是使用基于能力的系统,并要求每笔交易都重新续订相应能力。 Commit 消息用于为当前应用状态计算一个密码学承诺,并将其写入下一个区块头。这带来了一些很有用的特性。对该状态的更新如果出现不一致,现在将表现为区块链分叉,这能捕获一整类编程错误。它也简化了安全轻客户端的开发,因为 Merkle 哈希证明可以通过对照区块哈希来验证,而区块哈希又是由法定人数签名确认的。 一个应用可以拥有多个 ABCI socket 连接。CometBFT 会为应用创建四个 ABCI 连接:一个用于在 mempool 广播期间验证交易,一个供共识引擎执行区块提案,一个用于为应用状态创建快照,另一个用于查询应用状态。 应用设计者显然需要非常谨慎地设计消息处理逻辑,才能构建出真正有用的区块链,但这种架构至少提供了一个明确的起点。下图展示了通过 ABCI 进行消息流转的方式。 ABCI

关于确定性

区块链交易处理逻辑必须是确定性的。如果应用逻辑不是确定性的,CometBFT 副本节点之间就无法达成共识。 以 Ethereum 上的 Solidity 为例,它之所以是区块链应用的一个很好的选择,其中一个重要原因就是它是一门完全确定性的编程语言。不过,通过避免以下非确定性来源,也同样可以使用 Java、C++、Python 或 Go 等现有流行语言来创建确定性应用:
  • 随机数生成器(在没有确定性种子的情况下)
  • 线程上的竞态条件(或者干脆完全避免线程)
  • 系统时钟
  • 未初始化内存(在 C 或 C++ 这类不安全编程语言中)
  • 浮点运算
  • 具有随机性的语言特性(例如 Go 中的 map 迭代)
虽然程序员可以通过谨慎编码来避免非确定性,但也可以为每种语言创建专用的 linter 或静态分析器来检查确定性。未来我们可能会与合作伙伴一起构建这类工具。

共识概览

CometBFT 采用 Tendermint 共识,这是一种易于理解、大体上异步的 BFT 共识算法。该算法遵循一个简单的状态机,其形式如下: 共识逻辑 参与该算法的角色被称为验证者;他们轮流提出交易区块并对其投票。区块会按链式结构提交,每个高度对应一个区块。某个区块可能提交失败,这时算法会进入下一个轮次,并由新的验证者为该高度提出区块。一个区块要成功提交,需要经历两个投票阶段;我们称之为预投票和预提交。 图中有一对人在跳波尔卡舞,因为验证者的行为有点像在跳波尔卡。当超过三分之二的验证者对同一个区块进行预投票时,我们称之为一次波尔卡。每一次预提交都必须由同一轮中的一次波尔卡作为依据。当超过 2/3 的验证者在同一轮中对同一个区块进行预提交时,该区块就会被提交。 验证者可能因多种原因无法提交区块;当前提议者可能离线,或者网络可能较慢。Tendermint 共识允许他们判断某个验证者应被跳过。验证者会等待一小段时间,以便在投票进入下一轮之前从提议者那里接收完整的提案区块。正是对超时机制的这种依赖,使 Tendermint 共识成为一种弱同步算法,而不是纯异步算法。不过,算法的其余部分是异步的,验证者只有在听到超过三分之二验证者集合的消息后才会继续推进。Tendermint 共识的一个简化之处在于,它使用同一套机制既可以提交区块,也可以跳转到下一轮。 在少于三分之一的验证者属于拜占庭行为的前提下,Tendermint 共识算法保证安全性永远不会被破坏,也就是说,验证者绝不会在同一高度提交互相冲突的区块。为此,它引入了一些锁定规则,用来约束流程图中哪些路径可以被采取。一旦验证者对某个区块进行了预提交,它就会被锁定在该区块上。随后:
  1. 它必须为自己锁定的区块进行预投票
  2. 只有在后续轮次中,该新区块出现一次波尔卡时,它才能解锁并对新区块进行预提交

权益

在许多系统中,并不是所有验证者在共识协议中都拥有相同的“权重”。因此,我们关注的并不是验证者数量上的三分之一或三分之二,而是总投票权重中的这些比例;这些权重在各个验证者之间未必均匀分布。 由于 CometBFT 可以复制任意应用,因此完全可以定义一种货币,并用这种货币来计量投票权重。当投票权重以某种原生货币计价时,这个系统通常被称为权益证明。应用中的逻辑可以强制验证者将其持有的货币“绑定”为一笔安全保证金;如果它们在共识协议中被发现行为不当,这笔保证金就可以被销毁。这为协议安全性增加了经济层面的约束,使人们能够量化违反“少于三分之一投票权重为拜占庭行为”这一假设所需付出的成本。 Cosmos 网络 的设计目标之一,就是在一系列以 ABCI 应用形式实现的加密货币之间使用这种权益证明机制。

What is CometBFT

CometBFT is software for securely and consistently replicating an application on many machines. By securely, we mean that CometBFT works as long as fewer than 1/3 of machines fail in arbitrary ways. By consistently, we mean that every non-faulty machine sees the same transaction log and computes the same state. Secure and consistent replication is a fundamental problem in distributed systems; it plays a critical role in the fault tolerance of a broad range of applications, from currencies to elections to infrastructure orchestration and beyond. The ability to tolerate machines failing in arbitrary ways, including becoming malicious, is known as Byzantine fault tolerance (BFT). The theory of BFT is decades old, but software implementations have only become popular recently, due largely to the success of “blockchain technology” like Bitcoin and Ethereum. Blockchain technology is just a reformalization of BFT in a more modern setting, with emphasis on peer-to-peer networking and cryptographic authentication. The name derives from the way transactions are batched in blocks, where each block contains a cryptographic hash of the previous one, forming a chain. CometBFT consists of two chief technical components: a blockchain consensus engine and a generic application interface. The consensus engine, which is based on the Tendermint consensus algorithm, ensures that the same transactions are recorded on every machine in the same order. The application interface, called the Application BlockChain Interface (ABCI), delivers the transactions to applications for processing. Unlike other blockchain and consensus solutions, which come pre-packaged with built-in state machines (like a fancy key-value store or a quirky scripting language), developers can use CometBFT for BFT state machine replication of applications written in whatever programming language and development environment is right for them. CometBFT is designed to be easy to use, simple to understand, highly performant, and useful for a wide variety of distributed applications.

CometBFT vs. X

CometBFT is broadly similar to two classes of software. The first class consists of distributed key-value stores, like Zookeeper, etcd, and Consul, which use non-BFT consensus. The second class is known as “blockchain technology” and consists of both cryptocurrencies like Bitcoin and Ethereum, and alternative distributed ledger designs like Hyperledger’s Burrow.

Zookeeper, etcd, Consul

Zookeeper, etcd, and Consul are all implementations of key-value stores atop a classical, non-BFT consensus algorithm. Zookeeper uses an algorithm called Zookeeper Atomic Broadcast, while etcd and Consul use the Raft log replication algorithm. A typical cluster contains 3-5 machines and can tolerate crash failures in fewer than 1/2 of the machines (e.g., 1 out of 3 or 2 out of 5), but even a single Byzantine fault can jeopardize the whole system. Each offering provides a slightly different implementation of a feature-rich key-value store, but all are generally focused on providing basic services to distributed systems, such as dynamic configuration, service discovery, locking, leader election, and so on. CometBFT is in essence similar software, but with two key differences:
  • It is Byzantine Fault Tolerant, meaning it can only tolerate fewer than 1/3 of machines failing, but those failures can include arbitrary behavior— including hacking and malicious attacks.
  • It does not specify a particular application, like a fancy key-value store. Instead, it focuses on arbitrary state machine replication, so developers can build the application logic that’s right for them, from key-value stores to cryptocurrency to e-voting platforms and beyond.

Bitcoin, Ethereum, etc.

The Tendermint consensus algorithm, adopted by CometBFT, emerged in the tradition of cryptocurrencies like Bitcoin, Ethereum, etc., with the goal of providing a more efficient and secure consensus algorithm than Bitcoin’s Proof of Work. In the early days, Tendermint consensus-based blockchains had a simple currency built in, and to participate in consensus, users had to “bond” units of the currency into a security deposit which could be revoked if they misbehaved—this is what made Tendermint consensus a Proof-of-Stake algorithm. Since then, CometBFT has evolved to be a general-purpose blockchain consensus engine that can host arbitrary application states. That means it can be used as a plug-and-play replacement for the consensus engines of other blockchain software. So one can take the current Ethereum code base, whether in Rust, Go, or Haskell, and run it as an ABCI application using CometBFT. Indeed, we did that with Ethereum. And we plan to do the same for Bitcoin, ZCash, and various other deterministic applications as well. Another example of a cryptocurrency application built on CometBFT is the Cosmos network.

Other Blockchain Projects

Fabric takes a similar approach to CometBFT, but is more opinionated about how the state is managed and requires that all application behavior runs in potentially many Docker containers, modules it calls “chaincode”. It uses an implementation of PBFT from a team at IBM that is augmented to handle potentially non-deterministic chaincode. It is possible to implement this Docker-based behavior as an ABCI app in CometBFT, though extending CometBFT to handle non-determinism remains for future work. Burrow is an implementation of the Ethereum Virtual Machine and Ethereum transaction mechanics, with additional features for a name registry, permissions, and native contracts, and an alternative blockchain API. It uses CometBFT as its consensus engine and provides a particular application state.

ABCI Overview

The Application BlockChain Interface (ABCI) allows for Byzantine Fault Tolerant replication of applications written in any programming language.

Motivation

Thus far, all blockchain “stacks” (such as Bitcoin) have had a monolithic design. That is, each blockchain stack is a single program that handles all the concerns of a decentralized ledger; this includes P2P connectivity, the “mempool” broadcasting of transactions, consensus on the most recent block, account balances, Turing-complete contracts, user-level permissions, etc. Using a monolithic architecture is typically bad practice in computer science. It makes it difficult to reuse components of the code, and attempts to do so result in complex maintenance procedures for forks of the codebase. This is especially true when the codebase is not modular in design and suffers from “spaghetti code”. Another problem with monolithic design is that it limits you to the language of the blockchain stack (or vice versa). In the case of Ethereum, which supports a Turing-complete bytecode virtual machine, it limits you to languages that compile down to that bytecode; while the list is growing, it is still very limited. In contrast, our approach is to decouple the consensus engine and P2P layers from the details of the state of the particular blockchain application. We do this by abstracting away the details of the application to an interface, which is implemented as a socket protocol.

Intro to ABCI

CometBFT, the “consensus engine”, communicates with the application via a socket protocol that satisfies the ABCI, the CometBFT Socket Protocol. To draw an analogy, let’s talk about a well-known cryptocurrency, Bitcoin. Bitcoin is a cryptocurrency blockchain where each node maintains a fully audited Unspent Transaction Output (UTXO) database. If one wanted to create a Bitcoin-like system on top of ABCI, CometBFT would be responsible for
  • Sharing blocks and transactions between nodes
  • Establishing a canonical/immutable order of transactions (the blockchain)
The application will be responsible for
  • Maintaining the UTXO database
  • Validating cryptographic signatures of transactions
  • Preventing transactions from spending non-existent transactions
  • Allowing clients to query the UTXO database
CometBFT is able to decompose the blockchain design by offering a very simple API (i.e., the ABCI) between the application process and consensus process. The ABCI consists of 3 primary message types that get delivered from the core to the application. The application replies with corresponding response messages. The messages are specified here: ABCI Message Types. The FinalizeBlock message is the workhorse of the application. Each transaction in the blockchain is finalized within this message. The application needs to validate each transaction received with the FinalizeBlock message against the current state, application protocol, and the cryptographic credentials of the transaction. FinalizeBlock only prepares the update to be made and does not change the state of the application. The state change is actually committed at a later stage, i.e., in the commit phase. The CheckTx message is used for validating transactions. CometBFT’s mempool first checks the validity of a transaction with CheckTx and only relays valid transactions to its peers. For instance, an application may check an incrementing sequence number in the transaction and return an error upon CheckTx if the sequence number is old. Alternatively, they might use a capabilities-based system that requires capabilities to be renewed with every transaction. The Commit message is used to compute a cryptographic commitment to the current application state, to be placed into the next block header. This has some handy properties. Inconsistencies in updating that state will now appear as blockchain forks, which catches a whole class of programming errors. This also simplifies the development of secure lightweight clients, as Merkle-hash proofs can be verified by checking against the block hash, and the block hash is signed by a quorum. There can be multiple ABCI socket connections to an application. CometBFT creates four ABCI connections to the application: one for the validation of transactions when broadcasting in the mempool, one for the consensus engine to run block proposals, one for creating snapshots of the application state, and one more for querying the application state. It’s probably evident that application designers need to very carefully design their message handlers to create a blockchain that does anything useful, but this architecture provides a place to start. The diagram below illustrates the flow of messages via ABCI. abci

A Note on Determinism

The logic for blockchain transaction processing must be deterministic. If the application logic weren’t deterministic, consensus would not be reached among the CometBFT replica nodes. Solidity on Ethereum is a great language of choice for blockchain applications because, among other reasons, it is a completely deterministic programming language. However, it’s also possible to create deterministic applications using existing popular languages like Java, C++, Python, or Go by avoiding sources of non-determinism such as:
  • random number generators (without deterministic seeding)
  • race conditions on threads (or avoiding threads altogether)
  • the system clock
  • uninitialized memory (in unsafe programming languages like C or C++)
  • floating point arithmetic
  • language features that are random (e.g., map iteration in Go)
While programmers can avoid non-determinism by being careful, it is also possible to create a special linter or static analyzer for each language to check for determinism. In the future, we may work with partners to create such tools.

Consensus Overview

CometBFT adopts the Tendermint consensus, an easy-to-understand, mostly asynchronous, BFT consensus algorithm. The algorithm follows a simple state machine that looks like this: consensus-logic Participants in the algorithm are called validators; they take turns proposing blocks of transactions and voting on them. Blocks are committed in a chain, with one block at each height. A block may fail to be committed, in which case the algorithm moves to the next round, and a new validator gets to propose a block for that height. Two stages of voting are required to successfully commit a block; we call them pre-vote and pre-commit. There is a picture of a couple doing the polka because validators are doing something like a polka dance. When more than two-thirds of the validators pre-vote for the same block, we call that a polka. Every pre-commit must be justified by a polka in the same round. A block is committed when more than 2/3 of validators pre-commit for the same block in the same round. Validators may fail to commit a block for a number of reasons; the current proposer may be offline, or the network may be slow. Tendermint consensus allows them to establish that a validator should be skipped. Validators wait a small amount of time to receive a complete proposal block from the proposer before voting to move to the next round. This reliance on a timeout is what makes Tendermint consensus a weakly synchronous algorithm, rather than an asynchronous one. However, the rest of the algorithm is asynchronous, and validators only make progress after hearing from more than two-thirds of the validator set. A simplifying element of Tendermint consensus is that it uses the same mechanism to commit a block as it does to skip to the next round. Assuming fewer than one-third of the validators are Byzantine, the Tendermint consensus algorithm guarantees that safety will never be violated—that is, validators will never commit conflicting blocks at the same height. To do this, it introduces a few locking rules which modulate which paths can be followed in the flow diagram. Once a validator precommits a block, it is locked on that block. Then,
  1. it must prevote for the block it is locked on
  2. it can only unlock and precommit for a new block if there is a polka for that block in a later round

Stake

In many systems, not all validators will have the same “weight” in the consensus protocol. Thus, we are not so much interested in one-third or two-thirds of the validators, but in those proportions of the total voting power, which may not be uniformly distributed across individual validators. Since CometBFT can replicate arbitrary applications, it is possible to define a currency and denominate the voting power in that currency. When voting power is denominated in a native currency, the system is often referred to as Proof-of-Stake. Validators can be forced, by logic in the application, to “bond” their currency holdings in a security deposit that can be destroyed if they’re found to misbehave in the consensus protocol. This adds an economic element to the security of the protocol, allowing one to quantify the cost of violating the assumption that fewer than one-third of voting power is Byzantine. The Cosmos Network is designed to use this Proof-of-Stake mechanism across an array of cryptocurrencies implemented as ABCI applications.