We use a BFT consensus algorithm with deterministic finality in Flow for
- Consensus Nodes: deciding on the content of blocks
- Cluster of Collector Nodes: batching transactions into collections.
Flow uses a derivative of HotStuff (see paper HotStuff: BFT Consensus in the Lens of Blockchain (version 6) for the original algorithm). Our implementation is a mixture of Chained HotStuff and Event-Driven HotStuff with a few additional, minor modifications:
- We employ the rules for locking on blocks and block finalization from Event-Driven HotStuff. Specifically, we lock the newest block which has an (direct or indirect) 2-chain built on top. We finalize a block when it has a 3-chain built on top where with the first two chain links being direct.
- The way we progress though views follows the Chained HotStuff algorithm. A replica only votes or proposes for its current view.
- Flow's HotStuff does not implement
ViewChange
messages (akin to the approach taken in Streamlet: Textbook Streamlined Blockchains) - Flow uses a decentralized random beacon (based on Dfinity's proposal). The random beacon is run by Flow's consensus nodes and integrated into the consensus voting process.
In Flow, there are multiple HotStuff instances running in parallel. Specifically, the consensus nodes form a HotStuff committee and each collector cluster is its own committee. In the following, we refer to an authorized set of nodes, who run a particular HotStuff instance as a (HotStuff) committee
.
- Flow allows nodes to have different weights, reflecting how much they are trusted by the protocol.
The weight of a node can change over time due to stake changes or discovering protocol violations.
A
super-majority
of nodes is defined as a subset of the consensus committee, where the nodes have more than 2/3 of the entire committee's accumulated weight. - The messages from zero-weighted nodes are ignored by all committee members.
In addition to HotStuff's requirements on block validity, the Flow protocol adds additional requirements. For example, it is illegal to repeatedly include the same payload entities (e.g. collections, challenges, etc) in the same fork. Generally, payload entities expire. However within the expiry horizon, all ancestors of a block need to be known to verify that payload entities are not repeated.
We exclude the entire logic for determining payload validity from the HotStuff core implementation. This functionality is encapsulated in the Chain Compliance Layer (CCL) which precedes HotStuff. The CCL forwards a block to the HotStuff core logic only if
- the block's payload is valid,
- the block is connected to the most recently finalized block, and
- all ancestors have previously been processed by HotStuff.
If ancestors of a block are missing, the CCL caches the respective block and (iteratively) requests missing ancestors.
Payloads are generated outside of the HotStuff core logic. HotStuff only incorporates the payload root hash into the block header.
In Flow's HotStuff implementation, votes are used for two purposes:
- Prove that a super-majority of committee nodes considers the respective block a valid extension of the chain.
Therefore, nodes include a
StakingSignature
(BLS with curve BLS12-381) in their vote. - Construct a Source of Randomness as described in Dfinity's Random Beacon.
Therefore, consensus nodes include a
RandomBeaconSignature
(also BLS with curve BLS12-381, used in a threshold signature scheme) in their vote.
When the primary collects the votes, it verifies the StakingSignature
and the RandomBeaconSignature
.
If either signature is invalid, the entire vote is discarded. From all valid votes, the
StakingSignatures
and the RandomBeaconSignatures
are aggregated separately.
(note: Only the aggregation of the RandomBeaconSignature
into a threshold signature is currently implemented.
Aggregation of the BLS StakingSignatures
will be added later.)
- Following version 6 of the HotStuff paper,
replicas forward their votes for block
b
to the leader of the next view, i.e. the primary for viewb.View + 1
. - A proposer will attach its own vote for its proposal in the block proposal message (instead of signing the block proposal for authenticity and separately sending a vote).
For primary section, we use a randomized, weight-proportional selection.
HotStuff's core logic is broken down into multiple components. The figure below illustrates the dependencies of the core components and information flow between these components.
HotStuffEngine
andChainComplianceLayer
do not contain any core HotStuff logic. They are responsible for relaying HotStuff messages and validating block payloads respectively.EventLoop
buffers all incoming events, soEventHandler
can process one event at a time in a single thread.EventHandler
orchestrates all HotStuff components and implements HotStuff's state machine. The event handler is designed to be executed single-threaded.Communicator
relays outgoing HotStuff messages (votes and block proposals)Pacemaker
is a basic PaceMaker to ensure liveness by keeping the majority of committee replicas in the same viewForks
maintains an in-memory representation of all blocksb
, whose view is larger or equal to the view of the latest finalized block (known to this specific replica). As blocks with missing ancestors are cached outside of HotStuff (by the Chain Compliance Layer), all blocks stored inForks
are guaranteed to be connected to the genesis block (or the trusted checkpoint from which the replica started). Internally,Forks
consists of multiple layers:LevelledForest
: A blockchain forms a Tree. When removing all blocks with views strictly smaller than the last finalized block, the chain decomposes into multiple disconnected trees. In graph theory, such structure is a forest. To separate general graph-theoretical concepts from the concrete block-chain application,LevelledForest
refers to blocks as graphvertices
and to a block's view number aslevel
. TheLevelledForest
is an in-memory data structure to store and maintain a levelled forest. It provides functions to add vertices, query vertices by their ID (block's hash), query vertices by level, query the children of a vertex, and prune vertices by level (remove them from memory).Finalizer
: the finalizer uses theLevelledForest
to store blocks. The Finalizer tracks the locked and finalized blocks. Furthermore, it evaluates whether a block is safe to vote for.ForkChoice
: implements the fork choice rule. Currently,NewestForkChoice
always uses the quorum certificate (QC) with the largest view to build a new block (i.e. the fork a super-majority voted for most recently).Forks
is the highest level. It bundles theFinalizer
andForkChoice
into one component.
Validator
validates the HotStuff-relevant aspects of- QC: total weight of all signers is more than 2/3 of committee weight, validity of signatures, view number is strictly monotonously increasing
- block proposal: from designated primary for the block's respective view, contains proposer's vote for its own block, QC in block is valid
- vote: validity of signature, voter is has positive weight
VoteAggregator
caches votes on a per-block basis and builds QC if enough votes have been accumulated.Voter
tracks the view of the latest vote and determines whether or not to vote for a block (by callingforks.IsSafeBlock
)Committee
maintains the list of all authorized network members and their respective weight on a per-block basis. Furthermore, the committee contains the primary selection algorithm.BlockProducer
constructs the payload of a block, after the HotStuff core logic has decided which fork to extend
We have translated the Chained HotStuff protocol into a state machine shown below. The state machine is implemented
in EventHandler
.
The HotStuff state machine interacts with the PaceMaker, which triggers view changes. Conceptually, the PaceMaker
interfaces with the EventHandler
in two different modes:
- [asynchronous] On timeouts, the PaceMaker will emit a timeout event, which is processed as any other event (such as incoming blocks or votes) through the
EventLoop
. - [synchronous] When progress is made following the happy-path business logic, the
EventHandler
will inform the PaceMaker about completing the respective processing step via a direct method call (seePaceMaker interface
). If the PaceMaker changed the view in response, it returns aNewViewEvent
which will be synchronously processed by theEventHandler
.
Flow's PaceMaker is simple (compared to Libra's PaceMaker for instance). It solely relies on increasing the timeouts exponentially if no progress is made and exponentially decreasing timeouts on the happy path. The timeout values are limited by lower and upper-bounds to ensure that the PaceMaker can change from large to small timeouts in a reasonable number of views. The specific values for lower and upper timeout bounds are protocol-specified; we envision the bounds to be on the order of 1sec (lower bound) and multiple days (upper bound).
Progress, from the perspective of the PaceMaker is defined as entering view V
for which the replica knows a QC with V = QC.view + 1
.
In other words, we transition into the next view due to reaching quorum in the last view.
A central, non-trivial functionality of the PaceMaker is to skip views.
Specifically, given a QC with view qc.view
, the Pacemaker will skip ahead to view qc.view + 1
if currentView ≤ qc.view
.
All relevant code implementing the core HotStuff business logic is contained in /consensus/hotstuff/
(folder containing this README).
When starting to look into the code, we suggest starting with /consensus/hotstuff/event_loop.go
and /consensus/hotstuff/event_handler.go
.
All files in the /consensus/hotstuff/
folder, except for event_loop.go
and follower_loop.go
, are interfaces for HotStuff-related components.
The concrete implementations for all HotStuff-relevant components are in corresponding sub-folders.
For completeness, we list the component implemented in each subfolder below:
/consensus/hotstuff/blockproducer
builds a block proposal for a specified QC, interfaces with the logic for assembling a block payload, combines all relevant fields into a new block proposal./consensus/hotstuff/committee
maintains the list of all authorized network members and their respective weight on a per-block basis; contains the primary selection algorithm./consensus/hotstuff/eventhandler
orchestrates all HotStuff components and implements the HotStuff state machine. The event handler is designed to be executed single-threaded./consensus/hotstuff/follower
This component is only used by nodes that are not participating in the HotStuff committee. As Flow has dedicated node roles with specialized network functions, only a subset of nodes run the full HotStuff protocol. Nevertheless, all nodes need to be able to act on blocks being finalized. The approach we have taken for Flow is that block proposals are broadcast to all nodes (including non-committee nodes). Non-committee nodes locally determine block finality by applying HotStuff's finality rules. The HotStuff Follower contains the functionality to consume block proposals and trigger downstream processing of finalized blocks. The Follower does not actively participate in HotStuff./consensus/hotstuff/forks
maintains an in-memory representation of all blocksb
, whose view is larger or equal to the view of the latest finalized block (known to this specific HotStuff replica). It tracks the last finalized block, the currently locked block, evaluates whether it is safe to vote for a given block and provides a Fork-Choice when the replica is primary./consensus/hotstuff/helper
contains broadly-used helper functions for testing/consensus/hotstuff/integration
integration tests for verifying correct interaction of multiple HotStuff replicas/consensus/hotstuff/model
contains the HotStuff data models, including block proposal, QC, etc. Many HotStuff data models are built on top of basic data models defined in/model/flow/
./consensus/hotstuff/notifications
: All relevant events within the HotStuff logic are exported though a notification system. While the notifications are not used HotStuff-internally, they notify other components within the same node of relevant progress and are used for collecting HotStuff metrics./consensus/hotstuff/pacemaker
contains the implementation of Flow's basic PaceMaker, as described above./consensus/hotstuff/persister
for performance reasons, the implementation maintains the consensus state largely in-memory. Thepersister
stores the last entered view and the view of the latest voted block persistenlty on disk. This allows recovery after a crash without the risk of equivocation./consensus/hotstuff/runner
helper code for starting and shutting down the HotStuff logic safely in a multithreaded environment./consensus/hotstuff/validator
holds the logic for validating the HotStuff-relevant aspects of blocks, QCs, and votes/consensus/hotstuff/verification
contains integration of Flow's cryptographic primitives (signing and signature verification)/consensus/hotstuff/voteaggregator
caches votes on a per-block basis and builds a QC if enough votes have been accumulated./consensus/hotstuff/voter
tracks the view of the latest vote and determines whether or not to vote for a block
- BLS Aggregation of the
StakingSignatures
- include Epochs
- upgrade PaceMaker to include Timeout Certificates
- refactor crypto integration (code in
verification
and dependent modules) for better auditability
The HotStuff state machine exposes some details about its internal progress as notification through the hotstuff.Consumer
.
The following figure depicts at which points notifications are emitted.
We have implemented a telemetry system (hotstuff.notifications.TelemetryConsumer
) which implements the Consumer
interface.
The TelemetryConsumer
tracks all events as belonging together that were emitted during a path through the state machine.
Each path
through the state machine is identified by a unique id.
Generally, the TelemetryConsumer
could export the collected data to a variety of backends.
For now, we export the data to a logger.