github.com/cosmos/cosmos-sdk@v0.50.10/docs/architecture/adr-045-check-delivertx-middlewares.md (about) 1 # ADR 045: BaseApp `{Check,Deliver}Tx` as Middlewares 2 3 ## Changelog 4 5 * 20.08.2021: Initial draft. 6 * 07.12.2021: Update `tx.Handler` interface ([\#10693](https://github.com/cosmos/cosmos-sdk/pull/10693)). 7 * 17.05.2022: ADR is abandoned, as middlewares are deemed too hard to reason about. 8 9 ## Status 10 11 ABANDONED. Replacement is being discussed in [#11955](https://github.com/cosmos/cosmos-sdk/issues/11955). 12 13 ## Abstract 14 15 This ADR replaces the current BaseApp `runTx` and antehandlers design with a middleware-based design. 16 17 ## Context 18 19 BaseApp's implementation of ABCI `{Check,Deliver}Tx()` and its own `Simulate()` method call the `runTx` method under the hood, which first runs antehandlers, then executes `Msg`s. However, the [transaction Tips](https://github.com/cosmos/cosmos-sdk/issues/9406) and [refunding unused gas](https://github.com/cosmos/cosmos-sdk/issues/2150) use cases require custom logic to be run after the `Msg`s execution. There is currently no way to achieve this. 20 21 An naive solution would be to add post-`Msg` hooks to BaseApp. However, the Cosmos SDK team thinks in parallel about the bigger picture of making app wiring simpler ([#9181](https://github.com/cosmos/cosmos-sdk/discussions/9182)), which includes making BaseApp more lightweight and modular. 22 23 ## Decision 24 25 We decide to transform Baseapp's implementation of ABCI `{Check,Deliver}Tx` and its own `Simulate` methods to use a middleware-based design. 26 27 The two following interfaces are the base of the middleware design, and are defined in `types/tx`: 28 29 ```go 30 type Handler interface { 31 CheckTx(ctx context.Context, req Request, checkReq RequestCheckTx) (Response, ResponseCheckTx, error) 32 DeliverTx(ctx context.Context, req Request) (Response, error) 33 SimulateTx(ctx context.Context, req Request (Response, error) 34 } 35 36 type Middleware func(Handler) Handler 37 ``` 38 39 where we define the following arguments and return types: 40 41 ```go 42 type Request struct { 43 Tx sdk.Tx 44 TxBytes []byte 45 } 46 47 type Response struct { 48 GasWanted uint64 49 GasUsed uint64 50 // MsgResponses is an array containing each Msg service handler's response 51 // type, packed in an Any. This will get proto-serialized into the `Data` field 52 // in the ABCI Check/DeliverTx responses. 53 MsgResponses []*codectypes.Any 54 Log string 55 Events []abci.Event 56 } 57 58 type RequestCheckTx struct { 59 Type abci.CheckTxType 60 } 61 62 type ResponseCheckTx struct { 63 Priority int64 64 } 65 ``` 66 67 Please note that because CheckTx handles separate logic related to mempool priotization, its signature is different than DeliverTx and SimulateTx. 68 69 BaseApp holds a reference to a `tx.Handler`: 70 71 ```go 72 type BaseApp struct { 73 // other fields 74 txHandler tx.Handler 75 } 76 ``` 77 78 Baseapp's ABCI `{Check,Deliver}Tx()` and `Simulate()` methods simply call `app.txHandler.{Check,Deliver,Simulate}Tx()` with the relevant arguments. For example, for `DeliverTx`: 79 80 ```go 81 func (app *BaseApp) DeliverTx(req abci.RequestDeliverTx) abci.ResponseDeliverTx { 82 var abciRes abci.ResponseDeliverTx 83 ctx := app.getContextForTx(runTxModeDeliver, req.Tx) 84 res, err := app.txHandler.DeliverTx(ctx, tx.Request{TxBytes: req.Tx}) 85 if err != nil { 86 abciRes = sdkerrors.ResponseDeliverTx(err, uint64(res.GasUsed), uint64(res.GasWanted), app.trace) 87 return abciRes 88 } 89 90 abciRes, err = convertTxResponseToDeliverTx(res) 91 if err != nil { 92 return sdkerrors.ResponseDeliverTx(err, uint64(res.GasUsed), uint64(res.GasWanted), app.trace) 93 } 94 95 return abciRes 96 } 97 98 // convertTxResponseToDeliverTx converts a tx.Response into a abci.ResponseDeliverTx. 99 func convertTxResponseToDeliverTx(txRes tx.Response) (abci.ResponseDeliverTx, error) { 100 data, err := makeABCIData(txRes) 101 if err != nil { 102 return abci.ResponseDeliverTx{}, nil 103 } 104 105 return abci.ResponseDeliverTx{ 106 Data: data, 107 Log: txRes.Log, 108 Events: txRes.Events, 109 }, nil 110 } 111 112 // makeABCIData generates the Data field to be sent to ABCI Check/DeliverTx. 113 func makeABCIData(txRes tx.Response) ([]byte, error) { 114 return proto.Marshal(&sdk.TxMsgData{MsgResponses: txRes.MsgResponses}) 115 } 116 ``` 117 118 The implementations are similar for `BaseApp.CheckTx` and `BaseApp.Simulate`. 119 120 `baseapp.txHandler`'s three methods' implementations can obviously be monolithic functions, but for modularity we propose a middleware composition design, where a middleware is simply a function that takes a `tx.Handler`, and returns another `tx.Handler` wrapped around the previous one. 121 122 ### Implementing a Middleware 123 124 In practice, middlewares are created by Go function that takes as arguments some parameters needed for the middleware, and returns a `tx.Middleware`. 125 126 For example, for creating an arbitrary `MyMiddleware`, we can implement: 127 128 ```go 129 // myTxHandler is the tx.Handler of this middleware. Note that it holds a 130 // reference to the next tx.Handler in the stack. 131 type myTxHandler struct { 132 // next is the next tx.Handler in the middleware stack. 133 next tx.Handler 134 // some other fields that are relevant to the middleware can be added here 135 } 136 137 // NewMyMiddleware returns a middleware that does this and that. 138 func NewMyMiddleware(arg1, arg2) tx.Middleware { 139 return func (txh tx.Handler) tx.Handler { 140 return myTxHandler{ 141 next: txh, 142 // optionally, set arg1, arg2... if they are needed in the middleware 143 } 144 } 145 } 146 147 // Assert myTxHandler is a tx.Handler. 148 var _ tx.Handler = myTxHandler{} 149 150 func (h myTxHandler) CheckTx(ctx context.Context, req Request, checkReq RequestcheckTx) (Response, ResponseCheckTx, error) { 151 // CheckTx specific pre-processing logic 152 153 // run the next middleware 154 res, checkRes, err := txh.next.CheckTx(ctx, req, checkReq) 155 156 // CheckTx specific post-processing logic 157 158 return res, checkRes, err 159 } 160 161 func (h myTxHandler) DeliverTx(ctx context.Context, req Request) (Response, error) { 162 // DeliverTx specific pre-processing logic 163 164 // run the next middleware 165 res, err := txh.next.DeliverTx(ctx, tx, req) 166 167 // DeliverTx specific post-processing logic 168 169 return res, err 170 } 171 172 func (h myTxHandler) SimulateTx(ctx context.Context, req Request) (Response, error) { 173 // SimulateTx specific pre-processing logic 174 175 // run the next middleware 176 res, err := txh.next.SimulateTx(ctx, tx, req) 177 178 // SimulateTx specific post-processing logic 179 180 return res, err 181 } 182 ``` 183 184 ### Composing Middlewares 185 186 While BaseApp simply holds a reference to a `tx.Handler`, this `tx.Handler` itself is defined using a middleware stack. The Cosmos SDK exposes a base (i.e. innermost) `tx.Handler` called `RunMsgsTxHandler`, which executes messages. 187 188 Then, the app developer can compose multiple middlewares on top on the base `tx.Handler`. Each middleware can run pre-and-post-processing logic around its next middleware, as described in the section above. Conceptually, as an example, given the middlewares `A`, `B`, and `C` and the base `tx.Handler` `H` the stack looks like: 189 190 ```text 191 A.pre 192 B.pre 193 C.pre 194 H # The base tx.handler, for example `RunMsgsTxHandler` 195 C.post 196 B.post 197 A.post 198 ``` 199 200 We define a `ComposeMiddlewares` function for composing middlewares. It takes the base handler as first argument, and middlewares in the "outer to inner" order. For the above stack, the final `tx.Handler` is: 201 202 ```go 203 txHandler := middleware.ComposeMiddlewares(H, A, B, C) 204 ``` 205 206 The middleware is set in BaseApp via its `SetTxHandler` setter: 207 208 ```go 209 // simapp/app.go 210 211 txHandler := middleware.ComposeMiddlewares(...) 212 app.SetTxHandler(txHandler) 213 ``` 214 215 The app developer can define their own middlewares, or use the Cosmos SDK's pre-defined middlewares from `middleware.NewDefaultTxHandler()`. 216 217 ### Middlewares Maintained by the Cosmos SDK 218 219 While the app developer can define and compose the middlewares of their choice, the Cosmos SDK provides a set of middlewares that caters for the ecosystem's most common use cases. These middlewares are: 220 221 | Middleware | Description | 222 | ----------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | 223 | RunMsgsTxHandler | This is the base `tx.Handler`. It replaces the old baseapp's `runMsgs`, and executes a transaction's `Msg`s. | 224 | TxDecoderMiddleware | This middleware takes in transaction raw bytes, and decodes them into a `sdk.Tx`. It replaces the `baseapp.txDecoder` field, so that BaseApp stays as thin as possible. Since most middlewares read the contents of the `sdk.Tx`, the TxDecoderMiddleware should be run first in the middleware stack. | 225 | {Antehandlers} | Each antehandler is converted to its own middleware. These middlewares perform signature verification, fee deductions and other validations on the incoming transaction. | 226 | IndexEventsTxMiddleware | This is a simple middleware that chooses which events to index in Tendermint. Replaces `baseapp.indexEvents` (which unfortunately still exists in baseapp too, because it's used to index Begin/EndBlock events) | 227 | RecoveryTxMiddleware | This index recovers from panics. It replaces baseapp.runTx's panic recovery described in [ADR-022](./adr-022-custom-panic-handling.md). | 228 | GasTxMiddleware | This replaces the [`Setup`](https://github.com/cosmos/cosmos-sdk/blob/v0.43.0/x/auth/ante/setup.go) Antehandler. It sets a GasMeter on sdk.Context. Note that before, GasMeter was set on sdk.Context inside the antehandlers, and there was some mess around the fact that antehandlers had their own panic recovery system so that the GasMeter could be read by baseapp's recovery system. Now, this mess is all removed: one middleware sets GasMeter, another one handles recovery. | 229 230 ### Similarities and Differences between Antehandlers and Middlewares 231 232 The middleware-based design builds upon the existing antehandlers design described in [ADR-010](./adr-010-modular-antehandler.md). Even though the final decision of ADR-010 was to go with the "Simple Decorators" approach, the middleware design is actually very similar to the other [Decorator Pattern](./adr-010-modular-antehandler.md#decorator-pattern) proposal, also used in [weave](https://github.com/iov-one/weave). 233 234 #### Similarities with Antehandlers 235 236 * Designed as chaining/composing small modular pieces. 237 * Allow code reuse for `{Check,Deliver}Tx` and for `Simulate`. 238 * Set up in `app.go`, and easily customizable by app developers. 239 * Order is important. 240 241 #### Differences with Antehandlers 242 243 * The Antehandlers are run before `Msg` execution, whereas middlewares can run before and after. 244 * The middleware approach uses separate methods for `{Check,Deliver,Simulate}Tx`, whereas the antehandlers pass a `simulate bool` flag and uses the `sdkCtx.Is{Check,Recheck}Tx()` flags to determine in which transaction mode we are. 245 * The middleware design lets each middleware hold a reference to the next middleware, whereas the antehandlers pass a `next` argument in the `AnteHandle` method. 246 * The middleware design use Go's standard `context.Context`, whereas the antehandlers use `sdk.Context`. 247 248 ## Consequences 249 250 ### Backwards Compatibility 251 252 Since this refactor removes some logic away from BaseApp and into middlewares, it introduces API-breaking changes for app developers. Most notably, instead of creating an antehandler chain in `app.go`, app developers need to create a middleware stack: 253 254 ```diff 255 - anteHandler, err := ante.NewAnteHandler( 256 - ante.HandlerOptions{ 257 - AccountKeeper: app.AccountKeeper, 258 - BankKeeper: app.BankKeeper, 259 - SignModeHandler: encodingConfig.TxConfig.SignModeHandler(), 260 - FeegrantKeeper: app.FeeGrantKeeper, 261 - SigGasConsumer: ante.DefaultSigVerificationGasConsumer, 262 - }, 263 -) 264 +txHandler, err := authmiddleware.NewDefaultTxHandler(authmiddleware.TxHandlerOptions{ 265 + Debug: app.Trace(), 266 + IndexEvents: indexEvents, 267 + LegacyRouter: app.legacyRouter, 268 + MsgServiceRouter: app.msgSvcRouter, 269 + LegacyAnteHandler: anteHandler, 270 + TxDecoder: encodingConfig.TxConfig.TxDecoder, 271 +}) 272 if err != nil { 273 panic(err) 274 } 275 - app.SetAnteHandler(anteHandler) 276 + app.SetTxHandler(txHandler) 277 ``` 278 279 Other more minor API breaking changes will also be provided in the CHANGELOG. As usual, the Cosmos SDK will provide a release migration document for app developers. 280 281 This ADR does not introduce any state-machine-, client- or CLI-breaking changes. 282 283 ### Positive 284 285 * Allow custom logic to be run before an after `Msg` execution. This enables the [tips](https://github.com/cosmos/cosmos-sdk/issues/9406) and [gas refund](https://github.com/cosmos/cosmos-sdk/issues/2150) uses cases, and possibly other ones. 286 * Make BaseApp more lightweight, and defer complex logic to small modular components. 287 * Separate paths for `{Check,Deliver,Simulate}Tx` with different returns types. This allows for improved readability (replace `if sdkCtx.IsRecheckTx() && !simulate {...}` with separate methods) and more flexibility (e.g. returning a `priority` in `ResponseCheckTx`). 288 289 ### Negative 290 291 * It is hard to understand at first glance the state updates that would occur after a middleware runs given the `sdk.Context` and `tx`. A middleware can have an arbitrary number of nested middleware being called within its function body, each possibly doing some pre- and post-processing before calling the next middleware on the chain. Thus to understand what a middleware is doing, one must also understand what every other middleware further along the chain is also doing, and the order of middlewares matters. This can get quite complicated to understand. 292 * API-breaking changes for app developers. 293 294 ### Neutral 295 296 No neutral consequences. 297 298 ## Further Discussions 299 300 * [#9934](https://github.com/cosmos/cosmos-sdk/discussions/9934) Decomposing BaseApp's other ABCI methods into middlewares. 301 * Replace `sdk.Tx` interface with the concrete protobuf Tx type in the `tx.Handler` methods signature. 302 303 ## Test Cases 304 305 We update the existing baseapp and antehandlers tests to use the new middleware API, but keep the same test cases and logic, to avoid introducing regressions. Existing CLI tests will also be left untouched. 306 307 For new middlewares, we introduce unit tests. Since middlewares are purposefully small, unit tests suit well. 308 309 ## References 310 311 * Initial discussion: https://github.com/cosmos/cosmos-sdk/issues/9585 312 * Implementation: [#9920 BaseApp refactor](https://github.com/cosmos/cosmos-sdk/pull/9920) and [#10028 Antehandlers migration](https://github.com/cosmos/cosmos-sdk/pull/10028)