Besides raising public interest, blockchains have also recently gained traction in the scientific community. The underlying technology combines advances in several domains, most notably from distributed computing, cryptography, and economics, in order to provide novel solutions for achieving trust in decentralized and dynamic environments.
Our work has been originally motivated by Tezos [tezos-white-paper, tezos-tutorial], a blockchain platform which distinguishes itself by focusing on software security and providing upgradability. Regarding security, Tezos is mainly written in the OCaml programming language. OCaml has a robust static type system and a memory management system which help rule out potential runtime errors. OCaml also facilitates formal reasoning and mechanized proofs.
Regarding updgradability, Tezos offers an alternative to hard forks thanks to its self-amending capability. This feature makes it possible to build the chain in a democratic and controlled manner through a voting procedure. The self-amending mechanism also makes it appealing to use Tezos as a testbed for experimenting with different consensus algorithms, in order to understand their strengths, weaknesses, and suitability in the blockchain context.
Tezos relies upon a consensus mechanism based on a liquid
proof-of-stake system. Liquid proof-of-stake means that block
producing and voting rights are given to participants in proportion to
their stake, and that, furthermore, participants can delegate their
rights to other stake holders. As Nakamoto
consensus [bitcoin, GarayKL15], Tezos’ current consensus
algorithm [EmmyPlus_blog_post] achieves only probabilistic
finality111 A block is final if once appended to the blockchain
it will always remain in the chain. Probabilistic finality means
this property is guaranteed with overwhelming probability.
A block is final if once appended to the blockchain it will always remain in the chain. Probabilistic finality means this property is guaranteed with overwhelming probability.assuming an attacker with at most half of the total stake, and relying on a synchrony assumption.
The initial motivation of this work was to strengthen the resilience of Tezos through a classical BFT style consensus protocol in order to achieve deterministic finality while relaxing the synchrony assumption. We had two general requirements that we found that were missing in the existing BFT consensus protocols. First, for security reasons, message buffers need to be bounded: assuming unbounded buffers may lead to memory errors, which can be caused either accidentally or maliciously through spamming, for instance. Second, as previously observed in [TendermintCorrectness], plugging a classical BFT consensus protocol in a blockchain setting with a proof-of-stake boils down to solve a form of repeated consensus [delporte2008finite], where each consensus instance (i) produces a block, i.e. the decided value, and (ii) runs among a committee of processes which are selected based on their stake. To be applicable to open blockchains, committees need to be dynamic and change frequently.
In this work we explore and specify repeated consensus in blockchain settings by formally introducing the Dynamic Repeated Consensus (DRC) problem. We propose Tenderbake, a classical BFT style consensus algorithm which solves DRC and works with bounded buffers. Each contribution shows independent interests: Tenderbake can also be used as one-shot BFT consensus with bounded buffers, while DRC can also be used by other consensus algorithms.
We note that what is specific to dynamic repeated consensus is the selection function which chooses committees based on values already decided. This is possible because the (delegated) stake of each process is registered in the blockchain through specific transactions. Interestingly, and in contrast to state machine replication (SMR), DRC can be solved with always changing committees. Assuming committees that never stop changing is fundamental in blockchains for mainly two reasons: (i) it is not desirable to let a committee be responsible for producing blocks for too long, from both a fairness and a security perspective; (ii) the stake of participants is frequently changing, thus selecting based on stake provides dynamicity. For these reasons, we think that, in the context of open blockchains, DRC is a better notion than SMR. We discuss their relation more thoroughly later in the paper.
Regarding the system model, we assume that after an unknown stabilization time the system becomes synchronous and channels reliable, while before the system is asynchronous and channels lossy. During the synchronous period message delays are bounded by an unknown constant , while clock drifts are bounded to an unknown constant all the time.
Regarding Tenderbake, we note that it is inspired by Tendermint [tendermintv2, dissecting], which in turn is inspired by PBFT [pbft] and DLS [DLS88]. Tenderbake follows the rotating leader paradigm where the leader proposes value in a given round. Voting on the value relies upon locking, a classical mechanism already present in DLS, and upon view-change, a classical mechanism already present in PBFT. View-change allows to safely unlock and adopt another proposal when the leader is replaced. However, the view-change in Tendermint is inefficient: Tendermint terminates once processes synchronize in the same round after , and this happens in the worst case in rounds, where is the size of the committee. A Tenderbake consensus instance terminates in rounds, where is the upper bound on the number of Byzantine processes.
We also present a synchronization mechanism to bound buffer size and to guarantee that in the synchronous period correct processes are in the same round for a sufficiently large time window. We implement the synchronization mechanism by carefully managing timers and clock synchronization. This allows processes to discard all the messages not associated to their current or next round. We emphasize that in the proposed system model the message delay in the synchronous period is not known. A standard procedure in this case is to use increasing timeouts, as done in many algorithms (e.g., [hotstuff, tendermintv2]), to catch up with . However, in contrast to Tenderbake, these algorithms do not provide bounded buffers, storing messages pertaining to an unbounded number of rounds. Relaxing the assumptions for achieving bounded buffers is left as future work. Still, we feel that our assumptions are reasonable enough especially since clock synchronization is a basic building block in practice.
2 Problem Definition and System Model
We consider a message-passing distributed system composed of a finite set of processes. Processes can be correct or faulty. A faulty process is affected by Byzantine behavior: it might deviate arbitrarily from the protocol it is supposed to follow. A correct process follows the protocol.
2.1 Problem definition
We consider a form of repeated consensus suitable for blockchains, called Dynamic Repeated Consensus.
Originally, repeated consensus was defined on a infinite sequence of consensus instances executed by the same set of processes, where processes have to agree on an infinitely growing sequence of decision values [delporte2008finite]. Dynamic repeated consensus, instead, considers that each consensus instance is executed by a potentially different set of processes where is a parameter of the problem. Said differently, given the -th consensus instance, only processes participate in the consensus instance proposing values and deciding a value , while any correct process in can only adopt . Both decided and adopted values are considered as output values. We assume that is a value that every correct process agrees upon a priori.
To select the set of processes that will run a consensus instance , each correct process has access to a deterministic selection function that returns a sequence of processes based on previous output values. The committee is given by for and by for , where is a problem parameter. Each process calls with its own decided value; however, since decided values are agreed upon, returns the same sequence when called by different correct process. We also note that the sets are potentially unrelated to each other, in particular any pair of subsequent committees may differ.
Dynamic repeated consensus, as repeated consensus, needs to satisfy three properties: agreement, validity and termination. Agreement and termination have the same formulation, however, validity needs to reflect the dynamicity aspect of committees. To this end, we define validity by means of two predicates. The first one is called . When given as input a value , returns true if the value has been proposed by a legitimate process, e.g. a process in . The second one is called . When given as input two subsequent output values , , returns true if is consistent with . This predicate takes into account the fact that an output value depends on the previous one, as commonly assumed in blockchains. For instance, when output values are blocks containing transactions, a valid block must include the identifier or hash of the previous block and transactions must not be in conflict with those already decided. For conciseness, we define to be a predicate that returns true if both and return true for . We note that the use of an application-defined predicate for stating validity already appears in [TendermintCorrectness, redbelly17].
The three properties a Dynamic Repeated Consensus algorithm must satisfy are as follows:
(termination) Every correct process has an infinite output.
(agreement) If and are the sequences of output values of two correct processes and , then is a prefix of or is a prefix of .
(validity) If is the sequence of output values of a correct process , then is satisfied for any .
We use to denote the -th element of the sequence .
2.2 Execution and communication model
We assume that in any execution at most processes can can be faulty, where is lower than one third of .
Processes have access to digital signing and hashing algorithms. For simplicity, we assume that cryptography is perfect: digital signatures cannot be forged and there are no hash collisions. Each process has an associated public/private key pair for signing and processes can be identified by their public keys.
We assume that correct processes execute in phases. The execution of a phase consists in broadcasting some messages (possibly none), retrieving messages, and updating the process state. We consider that message sending and state updating are instantaneous, because their execution time are negligible in comparison to message transmission delays.
We assume a partially synchronous system, where after some unknown time (known as the global stabilization time) the system becomes synchronous and channels reliable, that is, there is a finite unknown bound on the message transfer delay. Before the system is asynchronous and channels are lossy. Note that we could also consider a system which alternates between sufficiently long good periods, when the system is synchronous and channels are reliable, and bad periods, when the system is asynchronous and channels are lossy.
We assume that processes have access to local clocks and that these clocks are loosely synchronized: at any time, the difference between the real time and the local clock of a process is bound by some constant , which, as , is a priori unknown.
We assume the presence of two communication primitives built on top of point-to-point channels, where exchanged messages are authenticated. The first primitive is a best-effort broadcast primitive used by processes participating to a consensus instance and the second is a pull primitive which can be used by any process.
Broadcasting is done by invoking the primitive . This primitive provides the following guarantees: (i) integrity, meaning that each message is delivered at most once and only if it was previously broadcast by some process; (ii) validity, meaning that after if a correct process broadcast a message at time , then every correct process eventually receives by time . To simplify the presentation, it is assumed that a process also sends messages to itself.
Retrieving output values from other processes is done by invoking the primitive . This primitive provides the following guarantee: if a process invokes the pull primitive at some time after then it will eventually receive all the output values decided by correct processes before . The pull primitive can be used to also retrieve protocol-specific data associated with output values, such as the justification for a decided value. The primitive ensures that any process will eventually be able to get a sufficient large prefix of output values to check if it is a committee member for a given consensus instance. Moreover, since the broadcast primitive can be unreliable, the pull mechanism also ensures that a process that decided a value communicates it further to other processes. Let us note that the pull primitive can be implemented in such a way that the caller does not need to pull all output values, but only the ones that it misses.
We assume that processes have bounded memory. This implies that: (i) the incoming buffers for message retrieval are bounded, which in turn means that if a message is received when buffers are full then the message is dropped; and (ii) output values (both decided and adopted values) are not stored in volatile memory but on disk.
2.3 Relation between DRC and SMR
DRC can be used to solve SMR. The only caveat is that DRC does not impose any constraint on the membership of consensus instances: the consensus set can change infinitely often. On the other hand, for liveness, SMR needs the membership to stabilize: eventually the set remains fixed or changes slowly. The intuition is the following: if any process (a client) sends a request to execute an operation (a transaction in a blockchain system) and the process receiving the request does not belong to a consensus instance (it is not a server), cannot serve the request. has to contact the current consensus instance. Through the primitive, will ask for already decided output values to retrieve the history of consensus sets via the selection committee function. However, a race condition between always new output values and readings on the prefix can happen. What is needed is a period of time sufficiently long in which (i) either no new output values are decided or (ii) a sufficient number of consensus instances are served by the same set of processes. The second condition implies that the selection function should choose for each output prefix the same set of processes responsible for multiple subsequent consensus instances and eventually the same set for an infinite suffix. This condition is not compatible with the necessity in a blockchain context to not reveal process identities too much in advance. Moreover, since in blokchains consensus sets are determined on the basis of dynamic parameters such as the stake in relation to incentives, having fixed committees is an unrealistic assumption.
In summary, DRC can be viewed as a weaker problem than SMR, since it can be solved with constantly changing consensus sets ans it is suitable for blockchains. We note that repeated consensus [delporte2008finite] is a stricter form of DRC, assuming the same set of processes for the whole computation, straightforwardly solving SMR.
A blockchain is a sequence of chained blocks. We refer to the blockchain’s head as the last block in the sequence, and to a block’s level as the position of the block in the sequence, with the first block having level . We consider that a block has a header and a contents, which is typically a sequence of transactions. As the block contents is application specific, we do not model it further. Instead, we simply assume that a block contents represents some value to be agreed upon. The header includes the block’s level in the blockchain and the hash of the previous block, among other fields that we will detail later.
Tenderbake solves dynamic repeated consensus for blockchains: at each positive level, a consensus instance agrees on some value. However, it is not enough to agree on the block contents alone, as in this way one cannot relate block contents at subsequent levels. Therefore, the value to be agreed upon consists of a block contents of the currently proposed block and the hash of the previous block. The tuples represent the output values in from the DRP definition in Section 2.1. We note that, thanks to the hashes, agreement on the output values implies agreement on the blockchain, except for its head. We therefore consider that the primitive retrieves not only output values in , but also the blocks referred to by the hashes in these output values. Also, the block corresponding to is called genesis.
At a given level, the processes designated by the function are called bakers,222The terminology is inspired by Tezos, where block producers are called bakers. while processes which are not bakers are called observers. Contrary to bakers, observers are passive in the sense that they only receive new blocks and update their local blockchain accordingly.
We note that throughout the text “value” may refer either to an output value or to a block contents. The appropriate meaning should be clear from the context and from the notation: for block contents and for the tuples . Moreover, we often identify the proposed value with the tuple to be agreed upon, and leave implicit. This is because at a given consensus instance a process filters out all messages that do not refer to the same predecessor block, relative to the head of ’s blockchain.
For convenience we consider that . However, all the discussions and results can be generalized for the case . As custom, a set of bakers is called a quorum. Quorums have the property that they intersect in at least one correct baker. This property is refered to as quorum intersection [quorums].
Tenderbake consensus instance execute in rounds. At each round, a value is proposed by the baker whose turn comes in a round-robin fashion, where the order on bakers is given by the function. Rounds evolve in sequential phases. Each phase has a predetermined duration, given by the round the phase belong to. Concretely, the duration is , where is a strictly increasing function with domain and . This means in particular that for any duration , there is a round such that , that is, the duration of each phase in round is bigger than . Also, each process invokes the pull primitive regularly, every time units, where is some constant. We assume that the clock drifts to not affect the ability of a process to correctly measure intervals of time.
In practice, given estimatesof the real message delay and of the maximum message delay , we would choose such that: i) is slightly bigger than , ii) increases rapidly (e.g. exponentially) till it reaches , and iii) then it increase slowly (e.g. linearly) afterwards. Also, could be chosen bigger than , as it is not useful to invoke the pull primitive more often than once every time units.
There are three types of phases: PROPOSE, PREENDORSE, and ENDORSE, each with a corresponding type of message: for proposals, for preendorsements, and for endorsements. A forth type of message, , is for the re-transmission of preendorsements. Any message refers to some level, some round, some hash value, and some proposed value. A baker proposes, preendorses, and endorses a value (at some level and with some round) when the baker broadcasts a message of the corresponding type. Only one value per round can be proposed or (pre)endorsed. A set of at least (pre)endorsements from different bakers, but with the same level and round and for the same value is called a (pre)endorsement quorum certificate (QC).
We consider that messages are blocks. This is a design choice that has the advantage that values do not have to be sent again once decided.
Messages are digitally signed by the bakers that generate them. We assume that the blocks up to level contain the public keys of the bakers for level , and the genesis block contains the public keys of the bakers for levels to . In this way, QCs used at a given level can be checked by processes that are not more than blocks behind.
3.2 Overview of Tenderbake
Each process maintains a local copy of the blockchain. At each level, a process executes or observes one consensus instance. Upon noticing agreement on a value, the process appends the block containing this value to its blockchain. Note that this block is not necessarily agreed upon, as the agreed value only reflects agreement on the block contents and the hash of the previous block, not on the whole block. Agreement on the whole block is obtained implicitly at the next level, in case the hash of the block matches the hash contained in the next agreed value. Therefore, a process considers a block committed if it is not the head of its blockchain.
Within a consensus instance, if a baker receives a preendorsement QC for a value and round , then keeps track of as an endorsable value and of as an endorsable round. Similarly, if a baker receives a preendorsement QC for a value and round during the ENDORSE phase, then locks on the value , and it keeps track of as a locked value and of as a locked round. Note that the locked round stores the most recent round at which endorsed a value, while the endorsable round stores the most recent round that is aware of at which bakers may have endorsed a value.
In a nutshell, the execution of a round is as follows. During the PROPOSE phase, the designated proposer proposes a value which can be newly generated or an endorsable value from a previous round of the same consensus instance. During the PREENDORSE phase, a baker preendorses if it is not locked or if it is locked on at a previous round than ’s endorsable round (it does no preendorse if it locked and is newly generated). If a baker does not preendorse then it sends a message with the preendorsement QC that justifies its (more recent) locked round. During the ENDORSE phase, if bakers receive a preendorsement QC for , they lock on it and endorse it. If bakers receive an endorsement QC for , they decide .
Tenderbake inherits from classical BFT solutions the two voting phases per round and the locking mechanism. Tracking endorsable values is inherited from [tendermintv2]. In addition to these, Tenderbake distinguishes itself in a few aspects which we detail next.
Due to the broadcast primitive being unreliable before , it can happen that a correct baker locks on some value but the other bakers do not set their endorsable value accordingly nor do they receive the messages that made lock. This could generate the same livelock issue [TendermintCorrectness] as in a previous version of Tendermint which did not use endorsable values. Therefore, and possibly messages carry a preendorsement QC that justifies that the included value is an endorsable value. These QCs are then used by bakers to update their endorsable values even when preendorsement messages have been lost. Note that Tendermint does not need such QCs because it assumes reliable communication even in the asynchronous period.
To achieve termination faster, when a baker refuses a proposal because it is locked on a higher round than the endorsable round of the proposed value, it broadcasts a message. This message contains a preendorsement QC that justifies its higher locked round. During the next round, bakers use this QC to set their endorsable value to the one with the highest round. The consensus instance can then terminate with the first correct proposer. Consequently, in the worst case scenario, i.e. when the first bakers are Byzantine, Tenderbake terminates in rounds after , assuming that processes have achieved round synchronization and that the round durations are sufficiently large.
One standard way to achieve termination is to have processes synchronize on the round for a sufficiently long period of time [DLS88, pbft, lumiere]. Given that processes start at the same time and have loosely synchronized clocks, and that rounds have the same duration, processes are synchronized at the beginning of their execution. However, they might desynchronize when at some level they do not all decide at the same round, and therefore some processes increment their level sooner than others. To resynchronize, in Tenderbake processes eventually adopt the smallest round at which some process has decided at level . To this end, block headers contain the round at which they are generated and processes invoke the primitive whenever they notice they are behind, that is, whenever they see a block at level with a smaller round than their own block at level . Given the updated round at level , and again thanks to the synchronized clocks and fixed round durations, they can infer at which round process is at level , and update their own round at level accordingly, thus synchronizing with . The same principle can be applied even if processes are not at the same level.
We note that the resynchronization mechanism described above involves only updating the head the blockchain and not previous blocks, as previous blocks are already agreed upon.
In the presence of dynamic committees, it is not enough that processes call punctually, when behind: assume that a baker decides at level but the others are not aware of this and have not decided, because the relevant messages were lost; assume also that is no longer a baker at , consequently, it no longer broadcasts messages and thus the other processes cannot progress. To solve this, the pull mechanism is triggered regularly.
For processes to be able to check that blocks received by calling are already agreed upon, each block comes with an endorsement QC for the block at the previous level. Furthermore, for the same reason, in response to a pull request, a process also attaches the endorsement QC that justifies the value in the head of the blockchain. Recall that a block is a
message, which contains a value that has not yet been decided at the moment the message is sent, thus the endorsement QCs cannot justify the current value.
3.3 Messages and blocks
We write messages using the following syntax: , where is , , , or , is the process that sent the message, and are the level and respectively the round during which the message is generated, is the hash the block at level , and is the type specific content of the message. Next, we describe the payloads for each type of message.
The payload of a message contains the endorsement quorum that justifies the block at the previous level. The payload also contains the proposed value to be agreed on and, in case is a previously proposed value, the corresponding endorsable round and the preendorsement QC that justifies . If the proposed value is new, then is and is the empty set.
Given a message, we denote by the corresponding block, where the part before the semicolon denotes the block’s header and the part after it its contents. We emphasize that this distinction is only done for presentation purposes, in order to separate concerns. However, a block is a message, and a propose message is a block.
The payload of a message consists of the value to be agreed upon. The payload of an message consists of an endorsed value to and the preendorsement QC justifying it. Finally, the payload of a message consists of a preendorsement QC justifying some endorsable value and round.
3.4 Process state and initialization
A process maintains a message buffer, its local copy of the blockchain, and a few more variables which we detail below. The message buffer of is represented by the variable which stores messages in set. The blockchain of is represented by the variable , a sequence of blocks where indices correspond to levels in the blockchain. We assume that the blockchain is stored on disk, not in the volatile memory. In contrast, variables are stored in memory. We detail them next.
The variables and store p’s current level and round. The variable stores the hash of the last block in the blockchain, that is, of the block at level .
The variable stores the endorsement QC for its last decided value. This variable is used when responds to a blockchain pull request: in addition to (the part of) the blockchain (that the requester is missing), also sends its to justify its last decided value.
The variables and are used by the locking mechanism to keep track respectively of the value on which is locked and the round during which locked on it. When , we say that a process is unlocked.
The variable keeps track of a proposed value with a preendorsement QC at a round , and therefore can be considered endorsable. In this case, the variables and store the round and the preendorsement QC, respectively. Note that the values and can be recovered from . We only use them for presentation reasons.
The variable stores the delay with which a phase starts.
The state of a process is initialized by the procedure init, given in Fig. 1. Besides initializing the state of a process, the procedure init schedules the pull mechanism to take place at a given interval .
In the pseudocode, we use to represent the value of an undefined variable. By abuse of notation, denotes that has become undefined. The (in)equality () represents the check whether is (un)defined. We use the function to return hashes. As a convention, we use the keyword proc to denote subprocedures that do not return a value, and the keyword fun to denote subprocedures that return a value. The function return the hash of the value . For a process , variables with the subscript represent the global variables that the process maintains.
After initialization, a process starts executing the consensus algorithm by executing the procedure start which simply checks whether is a baker for the level and then proceeds accordingly with the first phase of either the baker’s or the observer’s main loop, described in Sections 3.7 and 3.8. We note that, in the pseudocode, the three phases are identified by corresponding labels and goto statements represent unconditional jumps to the referred label. We assume that all processes start at the same time, where this time is specified in the a priori agreed genesis block. For convenience, we assume it is 0.
3.5 Message management
During a phase, bakers and observers continuously process events to update their state. The event processing loop is implemented by the procedure, presented in Fig. 2. There are two kinds of events: message retrievals, represented by the event, and notifications of new chains, represented by the event.
Upon the retrieval of a new message , a process first checks if the level, round, and hash in ’s header match respectively ’s current level, either the current round or the next round, and the hash of the block at the previous level. If yes, then checks that the message is valid, with the procedure validMessage, detailed below. If is indeed valid then it is stored in the message buffer. Also, the variables , , and are updated in the procedure updateEndorsable if a preendorsement QC is observed for a higher round than the current . This can happen if there is already a preendorsement QC for the current proposed value in or if a preendorsement QC for a higher round than the current is attached to a , , or message (line 2).
The procedure validMessage checks each message for validity. A message is valid if is the proposer for level and round and if is an endorsement QC for level with the round, hash, and value matching those in ’s blockchain. Note that the endorsements in the QC should be produced by bakers for the previous level. Furthermore, either is empty and is ( is newly proposed), or the round, value, and hash from match , , and , respectively. Note that messages in and are required to be valid themselves, in particular, they are generated by bakers at levels and , respectively. Also, these validity checks ensure that the value satisfies the predicate from Section 2.1. The validity conditions for the other types of messages are similar, and thus omitted. We note, however, that for preendorsements and endorsements it is required that the corresponding proposal has been already received, so that it can be checked that the (pre)endorsed value matches the proposed one.
We note that invalid messages, messages not for the current level, and messages not for the current or next round are dropped, thus ensuring that message buffers are bounded. Messages for the next round are kept to cater for the possible clock drift. Messages from higher levels trigger to ask for the sender’s blockchain, because such messages “from the future” suggest that is behind (or that the sender is lying about being ahead).
Upon a new chain notification possibly updates its chain. The notification includes the received chain and an endorsement QC for the value and the round of ’s head. This justifies that the head’s value has indeed been agreed upon. The procedure validChain checks whether is valid and whether ’s head and match. A chain is valid if each block’s hash field is the hash of the predecessor block, and if the contained values satisfy the predicate . In particular, the endorsement QC contained in a block at level should consist of endorsements produced by bakers at level and the justified value should match the value contained in the block at level .
After checking the validity of , process checks whether is better than ’s local chain. A chain is better than ’s current blockchain if it is longer or if it has the same length, i.e. , but the block at the previous level (i.e. at ) has a smaller round, which means that some other correct baker has taken a decision sooner in terms of rounds. In both cases, is “behind” and thus needs to resynchronize. Therefore, adopts the better chain, and reinitializes its state by calling the updateBlockchain procedure, and then it attempts to synchronize by calling the function, detailed in Section 3.6. As we will show later (see Lemma 5), adopting another chain may involve only an minor reorganization of the blockchain: only the head of the blockchain may change, all other previous blocks do not. After updating its blockchain, updates its current round and executes the next phase as either a baker or an observer (see Sections 3.7 and 3.8) depending on whether is in the committee for .
Besides validMessage, we use following additional helper functions which we also present only informally:
returns the id of the baker that is the proposer for round at level .
value() returns the value contained in the payload of a message.
returns some value which is consistent with respect to the value contained in the last block of the blockchain of the process that calls this function. That is, holds (see Section 2.1), where are the output values corresponding to .
proposedValue() returns the current proposed value.
returns the round contained in the header of the block at level .
valueQC() and roundQC() return the value and the round from respectively.
head() and length() return the head, respectively the length of the chain .
now() returns the local clock value at the moment of the call.
, , and return the proposal, preendorsements, and respectively the endorsements contained in .
3.6 Process progression
At the end of each round a process calls the procedure advance, defined in Fig. 3 to increment either its level or its round, depending on whether it takes a decision. To this end, first checks if it received an endorsement QC for the current proposed value and hash. If this is the case, then decides at the current level. The corresponding state update is performed by the procedure . Namely, the block proposed at the current round is appended to the blockchain, is updated to store the endorsement QC that justifies the decision, is updated to store the hash of the new head of the blockchain, the level is incremented, and all other state variables are reinitialized. If cannot decide yet, then increments its round and empties its message buffer. In either case, starts a new round by executing the phase PREENDORSE, either of the baker procedure, or the observer procedure, according to whether it is a baker or an observer for the current level.
If a process updates its blockchain not by observing an endorsement QC at the current round, but by retrieving new blocks from some other process, then it could be that is behind with respect to the other processes. To catch up, uses its local clock and the rounds of all previous blocks to find out what is the round, the phase, and the time position within this phase at which is supposed to be. These are determined with the function . This function first determines the starting time of the current level and stores it in . It then finds the current round by checking incrementally, starting from round whether the round is the current round: is the current round if there is no time left to execute a higher round. Note that is also updated to represent the time at which round started. The same principle is used to determine the current phase. Furthermore, the time within the phase or in other words the delay of the current process with respect to the start of the phase is stored in and is computed as expected: the difference between the current clock and the starting time of the phase. Note that we have .
If blocks include the timestamp of their creation, then the sum at line 3 in can be more efficiently computed by adding the timestamp of the head and the duration of the rounds for the current level only. For simplicity, we chose to omit timestamps from block headers.
The variable is used to adjust for how long the procedure is executed. When phases execute in sequence, this duration equals the duration of the phase (recall that we assumed that state updates and message broadcasts take zero time). When a phase is executed after a resynchronization, then may be executed for a shorter time than the phase duration, to keep synchronized with the other processes. Note that is used only once after a call to : it is reset to at the end of the procedure. In other words, may be positive only when resynchronizes.
Fig. 4 illustrates the timeline of a process that increments its rounds and phases using the procedure advance, where represents the starting time of the round of level . The figure also illustrates the value of the variable after calling : it stores the elapsed period between the start of the current phase and the current time.
3.7 Tenderbake main loop
The main loop of a Tenderbake baker is given in Fig. 5. The loop body represents the execution of one round. A round’s three phases are generally executed in sequence, cyclically. Jumps can nevertheless occur when detects it is behind.
Each phase consists of a conditional broadcast followed by a call to (described in Section 3.5). In addition, the ENDORSE phase calls advance (described in Section 3.6). Next, we describe the conditions to broadcast in each phase.
In the PROPOSE phase, checks if it is the proposer for the current level and round (lines 5-5). If yes, then proposes either a new value , obtained with the function (line 5), or its if defined. In the latter case, includes the corresponding endorsable round, and the preendorsement QC that justifies it to the payload of its proposal. The payload also includes the endorsement QC to justify the decision for the previous level.
is unlocked (i.e. , and therefore the second disjunct at line 5 holds); or
is locked (i.e. ), was already proposed during some previous round (i.e. ), and:
is already locked on itself; or
is locked on and its locked round is smaller or equal than the round at which was initially proposed.
Note that in the second case, we know there is a preendorsement QC for and round , thanks to the validity check on the message. If the condition holds, then preendorses . If cannot preendorse because it is locked on some value with a higher locked round than , then broadcasts the preendorsement QC that justifies . If received on time, this information allows the next correct proposer to propose a value that passes the above checks for all correct bakers.
In the ENDORSE phase, checks if it received a preendorsement QC for the proposed value . If yes, updates its and and broadcasts its message, along with all the messages for (lines 5-5). Note also that in this case has already updated its endorsable value to and its endorsable round to while executing (lines 2-2).
Observers execute a similarly structured loop as bakers, but do not participate in the current consensus instance. Fig. 6 contains the code that is run by any observer . It serves two purposes: i) to keep the blockchain at each observer up to date; ii) to check if is among the bakers in charge to agree at the next level. To achieve i), the observer checks if it can decide or adopt a proposed value. It does so in the same way as a baker, by invoking the and advance procedures. Concerning ii), when the corresponding check (lines 2 and 3) is successful, switches roles and acts as a baker, running the code from Fig. 5.
4.1 Bounded Memory
We recall that all values referred to by global or local variables of a process are stored in volatile memory, except for the variable whose value is stored on disk.
The following lemma shows that a process can use fixed-sized buffers, namely of size . We recall that the message buffer is represented by the variable.
For any correct process , at any time, .
For any correct process, at any time, the size of its volatile memory is in .
A correct process maintains a constant number of variables, and each variable different from either stores a primitive value or a QC. A QC contains at most messages and each message has either a constant size or its size is in . In particular, an endorsement contains a preendorsement QC. However, a preendorsement has constant size. The bound follows from these observations combined with the one from the argument in the proof of Lemma 1.
Each message includes some proposed value, and such values can have a large size. An important optimization is for preendorsements and endorsements to only include the hash of the proposed value, which has constant size. However, in this case, a baker needs to store previous proposed values in order to be able to check the hashes in preendorsements and endorsements. Therefore, a process may need unbounded memory. Nevertheless, the message buffer would still have constant size. Furthermore, a process would not store values from future rounds. This is more important than not storing values from past rounds, because it prevents a process from being spammed.
4.2 Message Complexity
Let be the number of processes in the system. Each round has a message complexity of due to the -to- broadcast.
It is known that consensus, in the worst case scenario, cannot be reached in less than rounds [fischer1982lower]. In Tenderbake, after bakers synchronize and the round durations are sufficiently long (namely, at least , a decision is taken in at most rounds, as already mentioned in Section 3.2. See Lemma 10 in Section 5 for a proof. Intuitively, rounds are needed in case the proposers of these rounds are Byzantine. Another round is needed if there is a correct process locked on a higher round than the endorsable round of the proposed value. However, in this case, the next proposer is correct and will have updated its endorsable round, and therefore its proposed value will be accepted and decided by all correct processes.
Correct processes need at most time to synchronize after , where is the maximum time needed for a correct process to receive the replies to its pull request. Indeed, a correct process sends a pull request after at most at time . The replies obtained from this pull request allow the process to synchronize. Note that depends on . Indeed, suppose that at the “slowest” correct process is still at level , while the “fastest” correct process is at level . Clearly the maximal value of is proportional to : it is roughly assuming that always reaches a decision at round . If we further assume that the time to receive a reply for a pull request is proportional to the size of reply, that is, to the number of blocks being sent, then we obtain that depends on , and therefore on .
The number of rounds until the round duration is large enough depends on ’s growth. If grows exponentially then this number is approximately .
The space required by a QC may be reduced by using threshold signatures which has the effect of reducing the message size from to . Note however that this technique requires threshold keys to be generated a priori, for example using a distributed key generation algorithm.
Since knowledge of the committee participants and their public keys is known a priori, it is possible to use aggregated signatures formed by signing with standard keys, along with a bitfield which represents the presence or absence of a participant’s signature. Then aggregated signatures can be used instead of threshold signatures with similar effect besides the extra space required for the bitfield.
The use of threshold signatures can be combined with a restructuring of the communication pattern within a round to also reduce the message complexity, as done for instance in HotStuff [hotstuff]: processes send their (pre)endorsements to the proposer, who combines the received signature shares into one threshold signature.
5 Correctness proof
In this section we prove the correctness of Tenderbake. The proofs for the agreement and validity properties are inspired by [dissecting].
5.1 Validity and Agreement
Tenderbake satisfies validity.
A correct process filters out invalid messages (line 2). In particular, it filters out a propose message containing a value and a hash that do not satisfy the predicate . Therefore it cannot decide on tuples that do not satisfy this predicate. ∎
Correct bakers only preendorse and endorse once per round at a given level.
and messages are sent only at line 5 and line 5, respectively. Furthermore, a phase is executed only once for a given round. Indeed, phases are either executed sequentially: PROPOSE, then PREENDORSE, then ENDORSE, and then a new round begins within the phase PROPOSE (line 3); or a potentially non-sequential jump to a different phase (line 2) takes place. However, this jump occurs only when a event occurs, that is, when a chain reorganization occurs. We show next that, given the condition under which such a reorganization occurs (line 2), this jump is a forward jump (in level, round, or phase), not a backward jump.
Let be a process handling a new chain and let be the level of head(). Let be the round of and let be the round computed from the actual chain of . The process makes a jump only if the check in line 2 is satisfied: either or . In the latter case, let and be the values of the variable computed by