Skip to content
Cosmopediaby Unity Nodes
DocumentationCometBFT specificationcometbft/cometbft › spec › abciView on CometBFT specification ↗

Requirements for the Application


order: 3 title: Requirements for the Application

Requirements for the Application

Formal Requirements

Consensus Connection Requirements

This section specifies what CometBFT expects from the Application. It is structured as a set of formal requirements that can be used for testing and verification of the Application's logic.

Let p and q be two correct processes. Let r<sub>p</sub> (resp. r<sub>q</sub>) be a round of height h where p (resp. q) is the proposer. Let s<sub>p,h-1</sub> be p's Application's state committed for height h-1. Let v<sub>p</sub> (resp. v<sub>q</sub>) be the block that p's (resp. q's) CometBFT passes on to the Application via PrepareProposalRequest as proposer of round r<sub>p</sub> (resp r<sub>q</sub>), height h, also known as the raw proposal. Let u<sub>p</sub> (resp. u<sub>q</sub>) the possibly modified block p's (resp. q's) Application returns via PrepareProposalResponse to CometBFT, also known as the prepared proposal.

Process p's prepared proposal can differ in two different rounds where p is the proposer.

  • Requirement 1 [PrepareProposal, timeliness]: If p's Application fully executes prepared blocks in PrepareProposal and the network is in a synchronous period while processes p and q are in r<sub>p</sub>, then the value of TimeoutPropose at q must be such that q's propose timer does not time out (which would result in q prevoting nil in r<sub>p</sub>).

Full execution of blocks at PrepareProposal time stands on CometBFT's critical path. Thus, Requirement 1 ensures the Application or operator will set a value for TimeoutPropose such that the time it takes to fully execute blocks in PrepareProposal does not interfere with CometBFT's propose timer. Note that the violation of Requirement 1 may lead to further rounds, but will not compromise liveness because even though TimeoutPropose is used as the initial value for proposal timeouts, CometBFT will be dynamically adjust these timeouts such that they will eventually be enough for completing PrepareProposal.

  • Requirement 2 [PrepareProposal, tx-size]: When p's Application calls PrepareProposal, the total size in bytes of the transactions returned does not exceed PrepareProposalRequest.max_tx_bytes.

Busy blockchains might seek to gain full visibility into transactions in CometBFT's mempool, rather than having visibility only on a subset of those transactions that fit in a block. The application can do so by setting ConsensusParams.Block.MaxBytes to -1. This instructs CometBFT (a) to enforce the maximum possible value for MaxBytes (100 MB) at CometBFT level, and (b) to provide all transactions in the mempool when calling PrepareProposal. Under these settings, the aggregated size of all transactions may exceed PrepareProposalRequest.max_tx_bytes. Hence, Requirement 2 ensures that the size in bytes of the transaction list returned by the application will never cause the resulting block to go beyond its byte size limit.

  • Requirement 3 [PrepareProposal, ProcessProposal, coherence]: For any two correct processes p and q, if q's CometBFT calls ProcessProposal on u<sub>p</sub>, q's Application returns Accept in ProcessProposalResponse.

Requirement 3 makes sure that blocks proposed by correct processes always pass the correct receiving process's ProcessProposal check. On the other hand, if there is a deterministic bug in PrepareProposal or ProcessProposal (or in both), strictly speaking, this makes all processes that hit the bug byzantine. This is a problem in practice, as very often validators are running the Application from the same codebase, so potentially all would likely hit the bug at the same time. This would result in most (or all) processes prevoting nil, with the serious consequences on CometBFT's liveness that this entails. Due to its criticality, Requirement 3 is a target for extensive testing and automated verification.

  • Requirement 4 [ProcessProposal, determinism-1]: ProcessProposal is a (deterministic) function of the current state and the block that is about to be applied. In other words, for any correct process p, and any arbitrary block u, if p's CometBFT calls ProcessProposal on u at height h, then p's Application's acceptance or rejection exclusively depends on u and s<sub>p,h-1</sub>.

  • Requirement 5 [ProcessProposal, determinism-2]: For any two correct processes p and q, and any arbitrary block u, if p's (resp. q's) CometBFT calls ProcessProposal on u at height h, then p's Application accepts u if and only if q's Application accepts u. Note that this requirement follows from Requirement 4 and the Agreement property of consensus.

Requirements 4 and 5 ensure that all correct processes will react in the same way to a proposed block, even if the proposer is Byzantine. However, ProcessProposal may contain a bug that renders the acceptance or rejection of the block non-deterministic, and therefore prevents processes hitting the bug from fulfilling Requirements 4 or 5 (effectively making those processes Byzantine). In such a scenario, CometBFT's liveness cannot be guaranteed. Again, this is a problem in practice if most validators are running the same software, as they are likely to hit the bug at the same point. There is currently no clear solution to help with this situation, so the Application designers/implementers must proceed very carefully with the logic/implementation of ProcessProposal. As a general rule ProcessProposal SHOULD always accept the block.

According to the Tendermint consensus algorithm, currently adopted in CometBFT, a correct process can broadcast at most one precommit message in round r, height h. Since, as stated in the Methods section, ExtendVote is only called when the consensus algorithm is about to broadcast a non-nil precommit message, a correct process can only produce one vote extension in round r, height h. Let e<sup>r</sup><sub>p</sub> be the vote extension that the Application of a correct process p returns via ExtendVoteResponse in round r, height h. Let w<sup>r</sup><sub>p</sub> be the proposed block that p's CometBFT passes to the Application via ExtendVoteRequest in round r, height h.

  • Requirement 6 [ExtendVote, VerifyVoteExtension, coherence]: For any two different correct processes p and q, if q receives e<sup>r</sup><sub>p</sub> from p in height h, q's Application returns Accept in VerifyVoteExtensionResponse.

Requirement 6 constrains the creation and handling of vote extensions in a similar way as Requirement 3 constrains the creation and handling of proposed blocks. Requirement 6 ensures that extensions created by correct processes always pass the VerifyVoteExtension checks performed by correct processes receiving those extensions. However, if there is a (deterministic) bug in ExtendVote or VerifyVoteExtension (or in both), we will face the same liveness issues as described for Requirement 5, as Precommit messages with invalid vote extensions will be discarded.

  • Requirement 7 [VerifyVoteExtension, determinism-1]: VerifyVoteExtension is a (deterministic) function of the current state, the vote extension received, and the prepared proposal that the extension refers to. In other words, for any correct process p, and any arbitrary vote extension e, and any arbitrary block w, if p's (resp. q's) CometBFT calls VerifyVoteExtension on e and w at height h, then p's Application's acceptance or rejection exclusively depends on e, w and s<sub>p,h-1</sub>.

  • Requirement 8 [VerifyVoteExtension, determinism-2]: For any two correct processes p and q, and any arbitrary vote extension e, and any arbitrary block w, if p's (resp. q's) CometBFT calls VerifyVoteExtension on e and w at height h, then p's Application accepts e if and only if q's Application accepts e. Note that this requirement follows from Requirement 7 and the Agreement property of consensus.

Requirements 7 and 8 ensure that the validation of vote extensions will be deterministic at all correct processes. Requirements 7 and 8 protect against arbitrary vote extension data from Byzantine processes, in a similar way as Requirements 4 and 5 protect against arbitrary proposed blocks. Requirements 7 and 8 can be violated by a bug inducing non-determinism in VerifyVoteExtension. In this case liveness can be compromised. Extra care should be put in the implementation of ExtendVote and VerifyVoteExtension. As a general rule, VerifyVoteExtension SHOULD always accept the vote extension.

  • Requirement 9 [all, no-side-effects]: p's calls to PrepareProposal, ProcessProposal, ExtendVote, and VerifyVoteExtension at height h do not modify s<sub>p,h-1</sub>.

  • Requirement 10 [ExtendVote, FinalizeBlock, non-dependency]: for any correct process p, and any vote extension e that p received at height h, the computation of s<sub>p,h</sub> does not depend on e.

The call to correct process p's FinalizeBlock at height h, with block v<sub>p,h</sub> passed as parameter, creates state s<sub>p,h</sub>. Additionally, p's FinalizeBlock creates a set of transaction results T<sub>p,h</sub>.

  • Requirement 11 [FinalizeBlock, determinism-1]: For any correct process p, s<sub>p,h</sub> exclusively depends on s<sub>p,h-1</sub> and v<sub>p,h</sub>.

  • Requirement 12 [FinalizeBlock, determinism-2]: For any correct process p, the contents of T<sub>p,h</sub> exclusively depend on s<sub>p,h-1</sub> and v<sub>p,h</sub>.

Note that Requirements 11 and 12, combined with the Agreement property of consensus ensure state machine replication, i.e., the Application state evolves consistently at all correct processes.

Also, notice that neither PrepareProposal nor ExtendVote have determinism-related requirements associated. Indeed, PrepareProposal is not required to be deterministic:

  • u<sub>p</sub> may depend on v<sub>p</sub> and s<sub>p,h-1</sub>, but may also depend on other values or operations.
  • v<sub>p</sub> = v<sub>q</sub> ⇏ u<sub>p</sub> = u<sub>q</sub>.

Likewise, ExtendVote can also be non-deterministic:

  • e<sup>r</sup><sub>p</sub> may depend on w<sup>r</sup><sub>p</sub> and s<sub>p,h-1</sub>, but may also depend on other values or operations.
  • w<sup>r</sup><sub>p</sub> = w<sup>r</sup><sub>q</sub> ⇏ e<sup>r</sup><sub>p</sub> = e<sup>r</sup><sub>q</sub>

Mempool Connection Requirements

Let CheckTxCodes<sub>tx,p,h</sub> denote the set of result codes returned by p's Application, via CheckTxResponse, to successive calls to CheckTx occurring while the Application is at height h and having transaction tx as parameter. CheckTxCodes<sub>tx,p,h</sub> is a set since p's Application may return different result codes during height h. If CheckTxCodes<sub>tx,p,h</sub> is a singleton set, i.e. the Application always returned the same result code in CheckTxResponse while at height h, we define CheckTxCode<sub>tx,p,h</sub> as the singleton value of CheckTxCodes<sub>tx,p,h</sub>. If CheckTxCodes<sub>tx,p,h</sub> is not a singleton set, CheckTxCode<sub>tx,p,h</sub> is undefined. Let predicate OK(CheckTxCode<sub>tx,p,h</sub>) denote whether CheckTxCode<sub>tx,p,h</sub> is SUCCESS.

  • Requirement 13 [CheckTx, eventual non-oscillation]: For any transaction tx, there exists a boolean value b, and a height h<sub>stable</sub> such that, for any correct process p, CheckTxCode<sub>tx,p,h</sub> is defined, and OK(CheckTxCode<sub>tx,p,h</sub>) = b for any height h ≥ h<sub>stable</sub>.

Requirement 13 ensures that a transaction will eventually stop oscillating between CheckTx success and failure if it stays in p's mempool for long enough. This condition on the Application's behavior allows the mempool to ensure that a transaction will leave the mempool of all full nodes, either because it is expunged everywhere due to failing CheckTx calls, or because it stays valid long enough to be gossipped, proposed and decided. Although Requirement 13 defines a global h<sub>stable</sub>, application developers can consider such stabilization height as local to process p (h<sub>p,stable</sub>), without loss for generality. In contrast, the value of b MUST be the same across all processes.

Connection State

CometBFT maintains four concurrent ABCI connections, namely Consensus Connection, Mempool Connection, Info/Query Connection, and Snapshot Connection. It is common for an application to maintain a distinct copy of the state for each connection, which are synchronized upon Commit calls.

Concurrency

In principle, each of the four ABCI connections operates concurrently with one another. This means applications need to ensure access to state is thread safe. Both the default in-process ABCI client and the default Go ABCI server use a global lock to guard the handling of events across all connections, so they are not concurrent at all. This means whether your app is compiled in-process with CometBFT using the NewLocalClient, or run out-of-process using the SocketServer, ABCI messages from all connections are received in sequence, one at a time.

The existence of this global mutex means Go application developers can get thread safety for application state by routing all reads and writes through the ABCI system. Thus it may be unsafe to expose application state directly to an RPC interface, and unless explicit measures are taken, all queries should be routed through the ABCI Query method.

FinalizeBlock

When the consensus algorithm decides on a block, CometBFT uses FinalizeBlock to send the decided block's data to the Application, which uses it to transition its state, but MUST NOT persist it; persisting MUST be done during Commit.

The Application must remember the latest height from which it has run a successful Commit so that it can tell CometBFT where to pick up from when it recovers from a crash. See information on the Handshake here.

Commit

The Application should persist its state during Commit, before returning from it.

Before invoking Commit, CometBFT locks the mempool and flushes the mempool connection. This ensures that no new messages will be received on the mempool connection during this processing step, providing an opportunity to safely update all four connection states to the latest committed state at the same time.

CometBFT unlocks the mempool after it has finished updating for the new block, which occurs asynchronously from Commit. See Mempool Update for more information on what the update task does.

WARNING: if the ABCI app logic processing the Commit message sends a /broadcast_tx_sync or /broadcast_tx and waits for the response before proceeding, it will deadlock. Executing broadcast_tx calls involves acquiring the mempool lock that CometBFT holds during the Commit call. Synchronous mempool-related calls must be avoided as part of the sequential logic of the Commit function.

Candidate States

CometBFT calls PrepareProposal when it is about to send a proposed block to the network. Likewise, CometBFT calls ProcessProposal upon reception of a proposed block from the network. The proposed block's data that is disclosed to the Application by these two methods is the following:

  • the transaction list
  • the LastCommit referring to the previous block
  • the block header's hash (except in PrepareProposal, where it is not known yet)
  • list of validators that misbehaved
  • the block's timestamp
  • NextValidatorsHash
  • Proposer address

The Application may decide to immediately execute the given block (i.e., upon PrepareProposal or ProcessProposal). There are two main reasons why the Application may want to do this:

  • Avoiding invalid transactions in blocks. In order to be sure that the block does not contain any invalid transaction, there may be no way other than fully executing the transactions in the block as though it was the decided block.
  • Quick FinalizeBlock execution. Upon reception of the decided block via FinalizeBlock, if that same block was executed upon PrepareProposal or ProcessProposal and the resulting state was kept in memory, the Application can simply apply that state (faster) to the main state, rather than reexecuting the decided block (slower).

Excerpt (19967 of 59921 characters). Read the whole page on CometBFT specification ↗