github.com/cosmos/cosmos-sdk@v0.50.10/docs/architecture/adr-064-abci-2.0.md (about)

     1  # ADR 64: ABCI 2.0 Integration (Phase II)
     2  
     3  ## Changelog
     4  
     5  * 2023-01-17: Initial Draft (@alexanderbez)
     6  * 2023-04-06: Add upgrading section (@alexanderbez)
     7  * 2023-04-10: Simplify vote extension state persistence (@alexanderbez)
     8  * 2023-07-07: Revise vote extension state persistence (@alexanderbez)
     9  * 2023-08-24: Revise vote extension power calculations and staking interface (@davidterpay)
    10  
    11  ## Status
    12  
    13  ACCEPTED
    14  
    15  ## Abstract
    16  
    17  This ADR outlines the continuation of the efforts to implement ABCI++ in the Cosmos
    18  SDK outlined in [ADR 060: ABCI 1.0 (Phase I)](adr-060-abci-1.0.md).
    19  
    20  Specifically, this ADR outlines the design and implementation of ABCI 2.0, which
    21  includes `ExtendVote`, `VerifyVoteExtension` and `FinalizeBlock`.
    22  
    23  ## Context
    24  
    25  ABCI 2.0 continues the promised updates from ABCI++, specifically three additional
    26  ABCI methods that the application can implement in order to gain further control,
    27  insight and customization of the consensus process, unlocking many novel use-cases
    28  that previously not possible. We describe these three new methods below:
    29  
    30  ### `ExtendVote`
    31  
    32  This method allows each validator process to extend the pre-commit phase of the
    33  CometBFT consensus process. Specifically, it allows the application to perform
    34  custom business logic that extends the pre-commit vote and supply additional data
    35  as part of the vote, although they are signed separately by the same key.
    36  
    37  The data, called vote extension, will be broadcast and received together with the
    38  vote it is extending, and will be made available to the application in the next
    39  height. Specifically, the proposer of the next block will receive the vote extensions
    40  in `RequestPrepareProposal.local_last_commit.votes`.
    41  
    42  If the application does not have vote extension information to provide, it
    43  returns a 0-length byte array as its vote extension.
    44  
    45  **NOTE**: 
    46  
    47  * Although each validator process submits its own vote extension, ONLY the *proposer*
    48    of the *next* block will receive all the vote extensions included as part of the
    49    pre-commit phase of the previous block. This means only the proposer will
    50    implicitly have access to all the vote extensions, via `RequestPrepareProposal`,
    51    and that not all vote extensions may be included, since a validator does not
    52    have to wait for all pre-commits, only 2/3.
    53  * The pre-commit vote is signed independently from the vote extension.
    54  
    55  ### `VerifyVoteExtension`
    56  
    57  This method allows validators to validate the vote extension data attached to
    58  each pre-commit message it receives. If the validation fails, the whole pre-commit
    59  message will be deemed invalid and ignored by CometBFT.
    60  
    61  CometBFT uses `VerifyVoteExtension` when validating a pre-commit vote. Specifically,
    62  for a pre-commit, CometBFT will:
    63  
    64  * Reject the message if it doesn't contain a signed vote AND a signed vote extension
    65  * Reject the message if the vote's signature OR the vote extension's signature fails to verify
    66  * Reject the message if `VerifyVoteExtension` was rejected by the app
    67  
    68  Otherwise, CometBFT will accept the pre-commit message.
    69  
    70  Note, this has important consequences on liveness, i.e., if vote extensions repeatedly
    71  cannot be verified by correct validators, CometBFT may not be able to finalize
    72  a block even if sufficiently many (+2/3) validators send pre-commit votes for
    73  that block. Thus, `VerifyVoteExtension` should be used with special care.
    74  
    75  CometBFT recommends that an application that detects an invalid vote extension
    76  SHOULD accept it in `ResponseVerifyVoteExtension` and ignore it in its own logic.
    77  
    78  ### `FinalizeBlock`
    79  
    80  This method delivers a decided block to the application. The application must
    81  execute the transactions in the block deterministically and update its state
    82  accordingly. Cryptographic commitments to the block and transaction results,
    83  returned via the corresponding parameters in `ResponseFinalizeBlock`, are
    84  included in the header of the next block. CometBFT calls it when a new block
    85  is decided.
    86  
    87  In other words, `FinalizeBlock` encapsulates the current ABCI execution flow of
    88  `BeginBlock`, one or more `DeliverTx`, and `EndBlock` into a single ABCI method.
    89  CometBFT will no longer execute requests for these legacy methods and instead
    90  will just simply call `FinalizeBlock`.
    91  
    92  ## Decision
    93  
    94  We will discuss changes to the Cosmos SDK to implement ABCI 2.0 in two distinct
    95  phases, `VoteExtensions` and `FinalizeBlock`.
    96  
    97  ### `VoteExtensions`
    98  
    99  Similarly for `PrepareProposal` and `ProcessProposal`, we propose to introduce
   100  two new handlers that an application can implement in order to provide and verify
   101  vote extensions.
   102  
   103  We propose the following new handlers for applications to implement:
   104  
   105  ```go
   106  type ExtendVoteHandler func(sdk.Context, abci.RequestExtendVote) abci.ResponseExtendVote
   107  type VerifyVoteExtensionHandler func(sdk.Context, abci.RequestVerifyVoteExtension) abci.ResponseVerifyVoteExtension
   108  ```
   109  
   110  An ephemeral context and state will be supplied to both handlers. The
   111  context will contain relevant metadata such as the block height and block hash.
   112  The state will be a cached version of the committed state of the application and
   113  will be discarded after the execution of the handler, this means that both handlers
   114  get a fresh state view and no changes made to it will be written.
   115  
   116  If an application decides to implement `ExtendVoteHandler`, it must return a
   117  non-nil `ResponseExtendVote.VoteExtension`.
   118  
   119  Recall, an implementation of `ExtendVoteHandler` does NOT need to be deterministic,
   120  however, given a set of vote extensions, `VerifyVoteExtensionHandler` must be
   121  deterministic, otherwise the chain may suffer from liveness faults. In addition,
   122  recall CometBFT proceeds in rounds for each height, so if a decision cannot be
   123  made about about a block proposal at a given height, CometBFT will proceed to the
   124  next round and thus will execute `ExtendVote` and `VerifyVoteExtension` again for
   125  the new round for each validator until 2/3 valid pre-commits can be obtained.
   126  
   127  Given the broad scope of potential implementations and use-cases of vote extensions,
   128  and how to verify them, most applications should choose to implement the handlers
   129  through a single handler type, which can have any number of dependencies injected
   130  such as keepers. In addition, this handler type could contain some notion of
   131  volatile vote extension state management which would assist in vote extension
   132  verification. This state management could be ephemeral or could be some form of
   133  on-disk persistence.
   134  
   135  Example:
   136  
   137  ```go
   138  // VoteExtensionHandler implements an Oracle vote extension handler.
   139  type VoteExtensionHandler struct {
   140  	cdc   Codec
   141  	mk    MyKeeper
   142  	state VoteExtState // This could be a map or a DB connection object
   143  }
   144  
   145  // ExtendVoteHandler can do something with h.mk and possibly h.state to create
   146  // a vote extension, such as fetching a series of prices for supported assets.
   147  func (h VoteExtensionHandler) ExtendVoteHandler(ctx sdk.Context, req abci.RequestExtendVote) abci.ResponseExtendVote {
   148  	prices := GetPrices(ctx, h.mk.Assets())
   149  	bz, err := EncodePrices(h.cdc, prices)
   150  	if err != nil {
   151  		panic(fmt.Errorf("failed to encode prices for vote extension: %w", err))
   152  	}
   153  
   154  	// store our vote extension at the given height
   155  	//
   156  	// NOTE: Vote extensions can be overridden since we can timeout in a round.
   157  	SetPrices(h.state, req, bz)
   158  
   159  	return abci.ResponseExtendVote{VoteExtension: bz}
   160  }
   161  
   162  // VerifyVoteExtensionHandler can do something with h.state and req to verify
   163  // the req.VoteExtension field, such as ensuring the provided oracle prices are
   164  // within some valid range of our prices.
   165  func (h VoteExtensionHandler) VerifyVoteExtensionHandler(ctx sdk.Context, req abci.RequestVerifyVoteExtension) abci.ResponseVerifyVoteExtension {
   166  	prices, err := DecodePrices(h.cdc, req.VoteExtension)
   167  	if err != nil {
   168  		log("failed to decode vote extension", "err", err)
   169  		return abci.ResponseVerifyVoteExtension{Status: REJECT}
   170  	}
   171  
   172  	if err := ValidatePrices(h.state, req, prices); err != nil {
   173  		log("failed to validate vote extension", "prices", prices, "err", err)
   174  		return abci.ResponseVerifyVoteExtension{Status: REJECT}
   175  	}
   176  
   177  	// store updated vote extensions at the given height
   178  	//
   179  	// NOTE: Vote extensions can be overridden since we can timeout in a round.
   180  	SetPrices(h.state, req, req.VoteExtension)
   181  
   182  	return abci.ResponseVerifyVoteExtension{Status: ACCEPT}
   183  }
   184  ```
   185  
   186  #### Vote Extension Propagation & Verification
   187  
   188  As mentioned previously, vote extensions for height `H` are only made available
   189  to the proposer at height `H+1` during `PrepareProposal`. However, in order to
   190  make vote extensions useful, all validators should have access to the agreed upon
   191  vote extensions at height `H` during `H+1`.
   192  
   193  Since CometBFT includes all the vote extension signatures in `RequestPrepareProposal`,
   194  we propose that the proposing validator manually "inject" the vote extensions
   195  along with their respective signatures via a special transaction, `VoteExtsTx`,
   196  into the block proposal during `PrepareProposal`. The `VoteExtsTx` will be
   197  populated with a single `ExtendedCommitInfo` object which is received directly
   198  from `RequestPrepareProposal`.
   199  
   200  For convention, the `VoteExtsTx` transaction should be the first transaction in
   201  the block proposal, although chains can implement their own preferences. For
   202  safety purposes, we also propose that the proposer itself verify all the vote
   203  extension signatures it receives in `RequestPrepareProposal`.
   204  
   205  A validator, upon a `RequestProcessProposal`, will receive the injected `VoteExtsTx`
   206  which includes the vote extensions along with their signatures. If no such transaction
   207  exists, the validator MUST REJECT the proposal.
   208  
   209  When a validator inspects a `VoteExtsTx`, it will evaluate each `SignedVoteExtension`.
   210  For each signed vote extension, the validator will generate the signed bytes and
   211  verify the signature. At least 2/3 valid signatures, based on voting power, must
   212  be received in order for the block proposal to be valid, otherwise the validator
   213  MUST REJECT the proposal.
   214  
   215  In order to have the ability to validate signatures, `BaseApp` must have access
   216  to the `x/staking` module, since this module stores an index from consensus
   217  address to public key. However, we will avoid a direct dependency on `x/staking`
   218  and instead rely on an interface instead. In addition, the Cosmos SDK will expose
   219  a default signature verification method which applications can use:
   220  
   221  ```go
   222  type ValidatorStore interface {
   223  	GetPubKeyByConsAddr(context.Context, sdk.ConsAddress) (cmtprotocrypto.PublicKey, error)
   224  }
   225  
   226  // ValidateVoteExtensions is a function that an application can execute in
   227  // ProcessProposal to verify vote extension signatures.
   228  func (app *BaseApp) ValidateVoteExtensions(ctx sdk.Context, currentHeight int64, extCommit abci.ExtendedCommitInfo) error {
   229  	votingPower := 0
   230  	totalVotingPower := 0
   231  
   232  	for _, vote := range extCommit.Votes {
   233  		totalVotingPower += vote.Validator.Power
   234  
   235  		if !vote.SignedLastBlock || len(vote.VoteExtension) == 0 {
   236  			continue
   237  		}
   238  
   239  		valConsAddr := sdk.ConsAddress(vote.Validator.Address)
   240  		pubKeyProto, err := valStore.GetPubKeyByConsAddr(ctx, valConsAddr)
   241  		if err != nil {
   242  			return fmt.Errorf("failed to get public key for validator %s: %w", valConsAddr, err)
   243  		}
   244  
   245  		if len(vote.ExtensionSignature) == 0 {
   246  			return fmt.Errorf("received a non-empty vote extension with empty signature for validator %s", valConsAddr)
   247  		}
   248  
   249  		cmtPubKey, err := cryptoenc.PubKeyFromProto(pubKeyProto)
   250  		if err != nil {
   251  			return fmt.Errorf("failed to convert validator %X public key: %w", valConsAddr, err)
   252  		}
   253  
   254  		cve := cmtproto.CanonicalVoteExtension{
   255  			Extension: vote.VoteExtension,
   256  			Height:    currentHeight - 1, // the vote extension was signed in the previous height
   257  			Round:     int64(extCommit.Round),
   258  			ChainId:   app.GetChainID(),
   259  		}
   260  
   261  		extSignBytes, err := cosmosio.MarshalDelimited(&cve)
   262  		if err != nil {
   263  			return fmt.Errorf("failed to encode CanonicalVoteExtension: %w", err)
   264  		}
   265  
   266  		if !cmtPubKey.VerifySignature(extSignBytes, vote.ExtensionSignature) {
   267  			return errors.New("received vote with invalid signature")
   268  		}
   269  
   270  		votingPower += vote.Validator.Power
   271  	}
   272  
   273  	if (votingPower / totalVotingPower) < threshold {
   274  		return errors.New("not enough voting power for the vote extensions")
   275  	}
   276  
   277  	return nil
   278  }
   279  ```
   280  
   281  Once at least 2/3 signatures, by voting power, are received and verified, the
   282  validator can use the vote extensions to derive additional data or come to some
   283  decision based on the vote extensions.
   284  
   285  > NOTE: It is very important to state, that neither the vote propagation technique
   286  > nor the vote extension verification mechanism described above is required for
   287  > applications to implement. In other words, a proposer is not required to verify
   288  > and propagate vote extensions along with their signatures nor are proposers
   289  > required to verify those signatures. An application can implement it's own
   290  > PKI mechanism and use that to sign and verify vote extensions.
   291  
   292  #### Vote Extension Persistence
   293  
   294  In certain contexts, it may be useful or necessary for applications to persist
   295  data derived from vote extensions. In order to facilitate this use case, we propose
   296  to allow app developers to define a pre-Blocker hook which will be called
   297  at the very beginning of `FinalizeBlock`, i.e. before `BeginBlock` (see below).
   298  
   299  Note, we cannot allow applications to directly write to the application state
   300  during `ProcessProposal` because during replay, CometBFT will NOT call `ProcessProposal`,
   301  which would result in an incomplete state view.
   302  
   303  ```go
   304  func (a MyApp) PreBlocker(ctx sdk.Context, req *abci.RequestFinalizeBlock) error {
   305  	voteExts := GetVoteExtensions(ctx, req.Txs)
   306  	
   307  	// Process and perform some compute on vote extensions, storing any resulting
   308  	// state.
   309  	if err a.processVoteExtensions(ctx, voteExts); if err != nil {
   310  		return err
   311  	}
   312  }
   313  ```
   314  
   315  ### `FinalizeBlock`
   316  
   317  The existing ABCI methods `BeginBlock`, `DeliverTx`, and `EndBlock` have existed
   318  since the dawn of ABCI-based applications. Thus, applications, tooling, and developers
   319  have grown used to these methods and their use-cases. Specifically, `BeginBlock`
   320  and `EndBlock` have grown to be pretty integral and powerful within ABCI-based
   321  applications. E.g. an application might want to run distribution and inflation
   322  related operations prior to executing transactions and then have staking related
   323  changes to happen after executing all transactions.
   324  
   325  We propose to keep `BeginBlock` and `EndBlock` within the SDK's core module
   326  interfaces only so application developers can continue to build against existing
   327  execution flows. However, we will remove `BeginBlock`, `DeliverTx` and `EndBlock`
   328  from the SDK's `BaseApp` implementation and thus the ABCI surface area.
   329  
   330  What will then exist is a single `FinalizeBlock` execution flow. Specifically, in
   331  `FinalizeBlock` we will execute the application's `BeginBlock`, followed by
   332  execution of all the transactions, finally followed by execution of the application's
   333  `EndBlock`.
   334  
   335  Note, we will still keep the existing transaction execution mechanics within
   336  `BaseApp`, but all notions of `DeliverTx` will be removed, i.e. `deliverState`
   337  will be replace with `finalizeState`, which will be committed on `Commit`.
   338  
   339  However, there are current parameters and fields that exist in the existing
   340  `BeginBlock` and `EndBlock` ABCI types, such as votes that are used in distribution
   341  and byzantine validators used in evidence handling. These parameters exist in the
   342  `FinalizeBlock` request type, and will need to be passed to the application's
   343  implementations of `BeginBlock` and `EndBlock`.
   344  
   345  This means the Cosmos SDK's core module interfaces will need to be updated to
   346  reflect these parameters. The easiest and most straightforward way to achieve
   347  this is to just pass `RequestFinalizeBlock` to `BeginBlock` and `EndBlock`.
   348  Alternatively, we can create dedicated proxy types in the SDK that reflect these
   349  legacy ABCI types, e.g. `LegacyBeginBlockRequest` and `LegacyEndBlockRequest`. Or,
   350  we can come up with new types and names altogether.
   351  
   352  ```go
   353  func (app *BaseApp) FinalizeBlock(req abci.RequestFinalizeBlock) (*abci.ResponseFinalizeBlock, error) {
   354  	ctx := ...
   355  
   356  	if app.preBlocker != nil {
   357  		ctx := app.finalizeBlockState.ctx
   358  		rsp, err := app.preBlocker(ctx, req)
   359  		if err != nil {
   360  			return nil, err
   361  		}
   362  		if rsp.ConsensusParamsChanged {
   363  			app.finalizeBlockState.ctx = ctx.WithConsensusParams(app.GetConsensusParams(ctx))
   364  		}
   365  	}
   366  	beginBlockResp, err := app.beginBlock(req)
   367  	appendBlockEventAttr(beginBlockResp.Events, "begin_block")
   368  
   369  	txExecResults := make([]abci.ExecTxResult, 0, len(req.Txs))
   370  	for _, tx := range req.Txs {
   371  		result := app.runTx(runTxModeFinalize, tx)
   372  		txExecResults = append(txExecResults, result)
   373  	}
   374  
   375  	endBlockResp, err := app.endBlock(app.finalizeBlockState.ctx)
   376  	appendBlockEventAttr(beginBlockResp.Events, "end_block")
   377  
   378  	return abci.ResponseFinalizeBlock{
   379  		TxResults:             txExecResults,
   380  		Events:                joinEvents(beginBlockResp.Events, endBlockResp.Events),
   381  		ValidatorUpdates:      endBlockResp.ValidatorUpdates,
   382  		ConsensusParamUpdates: endBlockResp.ConsensusParamUpdates,
   383  		AppHash:               nil,
   384  	}
   385  }
   386  ```
   387  
   388  #### Events
   389  
   390  Many tools, indexers and ecosystem libraries rely on the existence `BeginBlock`
   391  and `EndBlock` events. Since CometBFT now only exposes `FinalizeBlockEvents`, we
   392  find that it will still be useful for these clients and tools to still query for
   393  and rely on existing events, especially since applications will still define
   394  `BeginBlock` and `EndBlock` implementations.
   395  
   396  In order to facilitate existing event functionality, we propose that all `BeginBlock`
   397  and `EndBlock` events have a dedicated `EventAttribute` with `key=block` and
   398  `value=begin_block|end_block`. The `EventAttribute` will be appended to each event
   399  in both `BeginBlock` and `EndBlock` events`. 
   400  
   401  
   402  ### Upgrading
   403  
   404  CometBFT defines a consensus parameter, [`VoteExtensionsEnableHeight`](https://github.com/cometbft/cometbft/blob/v0.38.0-alpha.1/spec/abci/abci%2B%2B_app_requirements.md#abciparamsvoteextensionsenableheight),
   405  which specifies the height at which vote extensions are enabled and **required**.
   406  If the value is set to zero, which is the default, then vote extensions are
   407  disabled and an application is not required to implement and use vote extensions.
   408  
   409  However, if the value `H` is positive, at all heights greater than the configured
   410  height `H` vote extensions must be present (even if empty). When the configured
   411  height `H` is reached, `PrepareProposal` will not include vote extensions yet,
   412  but `ExtendVote` and `VerifyVoteExtension` will be called. Then, when reaching
   413  height `H+1`, `PrepareProposal` will include the vote extensions from height `H`.
   414  
   415  It is very important to note, for all heights after H:
   416  
   417  * Vote extensions CANNOT be disabled
   418  * They are mandatory, i.e. all pre-commit messages sent MUST have an extension
   419    attached (even if empty)
   420  
   421  When an application updates to the Cosmos SDK version with CometBFT v0.38 support,
   422  in the upgrade handler it must ensure to set the consensus parameter
   423  `VoteExtensionsEnableHeight` to the correct value. E.g. if an application is set
   424  to perform an upgrade at height `H`, then the value of `VoteExtensionsEnableHeight`
   425  should be set to any value `>=H+1`. This means that at the upgrade height, `H`,
   426  vote extensions will not be enabled yet, but at height `H+1` they will be enabled.
   427  
   428  ## Consequences
   429  
   430  ### Backwards Compatibility
   431  
   432  ABCI 2.0 is naturally not backwards compatible with prior versions of the Cosmos SDK
   433  and CometBFT. For example, an application that requests `RequestFinalizeBlock`
   434  to the same application that does not speak ABCI 2.0 will naturally fail.
   435  
   436  In addition, `BeginBlock`, `DeliverTx` and `EndBlock` will be removed from the
   437  application ABCI interfaces and along with the inputs and outputs being modified
   438  in the module interfaces.
   439  
   440  ### Positive
   441  
   442  * `BeginBlock` and `EndBlock` semantics remain, so burden on application developers
   443    should be limited.
   444  * Less communication overhead as multiple ABCI requests are condensed into a single
   445    request.
   446  * Sets the groundwork for optimistic execution.
   447  * Vote extensions allow for an entirely new set of application primitives to be
   448    developed, such as in-process price oracles and encrypted mempools.
   449  
   450  ### Negative
   451  
   452  * Some existing Cosmos SDK core APIs may need to be modified and thus broken.
   453  * Signature verification in `ProcessProposal` of 100+ vote extension signatures
   454    will add significant performance overhead to `ProcessProposal`. Granted, the
   455  	signature verification process can happen concurrently using an error group
   456  	with `GOMAXPROCS` goroutines.
   457  
   458  ### Neutral
   459  
   460  * Having to manually "inject" vote extensions into the block proposal during
   461    `PrepareProposal` is an awkward approach and takes up block space unnecessarily.
   462  * The requirement of `ResetProcessProposalState` can create a footgun for
   463    application developers if they're not careful, but this is necessary in order
   464  	for applications to be able to commit state from vote extension computation.
   465  
   466  ## Further Discussions
   467  
   468  Future discussions include design and implementation of ABCI 3.0, which is a
   469  continuation of ABCI++ and the general discussion of optimistic execution.
   470  
   471  ## References
   472  
   473  * [ADR 060: ABCI 1.0 (Phase I)](adr-060-abci-1.0.md)