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)