Changelog
- 2020 Apr 24: Initial Draft
- 2021 Sep 14: Superseded by ADR-045
Status
SUPERSEDED by ADR-045Context
The current implementation of BaseApp does not allow developers to write custom error handlers during panic recovery runTx() method. We think that this method can be more flexible and can give Cosmos SDK users more options for customizations without the need to rewrite whole BaseApp. Also there’s one special case forsdk.ErrorOutOfGas error handling, that case
might be handled in a “standard” way (middleware) alongside the others.
We propose middleware-solution, which could help developers implement the following cases:
- add external logging (let’s say sending reports to external services like Sentry);
- call panic for specific error cases;
OutOfGas case and default case one of the middlewares.
Default case wraps recovery object to an error and logs it (example middleware implementation).
Our project has a sidecar service running alongside the blockchain node (smart contracts virtual machine). It is
essential that node <-> sidecar connectivity stays stable for TXs processing. So when the communication breaks we need
to crash the node and reboot it once the problem is solved. That behaviour makes node’s state machine execution
deterministic. As all keeper panics are caught by runTx’s defer() handler, we have to adjust the BaseApp code
in order to customize it.
Decision
Design
Overview
Instead of hardcoding custom error handling into BaseApp we suggest using set of middlewares which can be customized externally and will allow developers use as many custom error handlers as they want. Implementation with tests can be found here.Implementation details
Recovery handler
NewRecoveryHandler type added. recoveryObj input argument is an object returned by the standard Go function
recover() from the builtin package.
nil should be returned if input object can’t be handled by that RecoveryHandler (not a handler’s target type).
Not nil error should be returned if input object was handled and middleware chain execution should be stopped.
An example:
OutOfGas handler.
Recovery middleware
We also add a middleware type (decorator). That function type wrapsRecoveryHandler and returns the next middleware in
execution chain and handler’s error. Type is used to separate actual recovery() object handling from middleware
chain processing.
recoveryObj object and returns:
- (next
recoveryMiddleware,nil) if object wasn’t handled (not a target type) byRecoveryHandler; - (
nil, not nilerror) if input object was handled and other middlewares in the chain should not be executed; - (
nil,nil) in case of invalid behavior. Panic recovery might not have been properly handled; this can be avoided by always using adefaultas a rightmost middleware in the chain (always returns anerror’);
OutOfGas middleware example:
Default middleware example:
Recovery processing
Basic chain of middlewares processing would look like:default handler which must return an error.
BaseApp changes
Thedefault middleware chain must exist in a BaseApp object. Baseapp modifications:
RecoveryHandlers by providing AddRunTxRecoveryHandler as a BaseApp option parameter to the NewBaseapp constructor:
Consequences
Positive
- Developers of Cosmos SDK based projects can add custom panic handlers to:
- add error context for custom panic sources (panic inside of custom keepers);
- emit
panic(): passthrough recovery object to the Tendermint core; - other necessary handling;
- Developers can use standard Cosmos SDK
BaseAppimplementation, rather that rewriting it in their projects; - Proposed solution doesn’t break the current “standard”
runTx()flow;
Negative
- Introduces changes to the execution model design.
Neutral
OutOfGaserror handler becomes one of the middlewares;- Default panic handler becomes one of the middlewares;