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)