chore(RFC): add rfc for handlers (#19263)
Co-authored-by: testinginprod <frojdi@faulttolerance.net> Co-authored-by: Aleksandr Bezobchuk <aleks.bezobchuk@gmail.com>
This commit is contained in:
parent
f6efab91b4
commit
cf9acccb95
282
docs/rfc/rfc-006-handlers.md
Normal file
282
docs/rfc/rfc-006-handlers.md
Normal file
@ -0,0 +1,282 @@
|
||||
# RFC 006: Handlers
|
||||
|
||||
## Changelog
|
||||
|
||||
* January 26, 2024: Initialized
|
||||
|
||||
## Background
|
||||
|
||||
The Cosmos SDK has a very powerful and flexible module system that has been tested
|
||||
and proven to be very good in production. The design of how messages are handled
|
||||
is built around Protobuf services and gRPC. This design was proposed and implemented
|
||||
during a time when we migrated from Amino to Protocol Buffers. This design has
|
||||
fulfilled the needs of users today. While this design is useful it has caused a
|
||||
elevated learning curve to be adopted by users. Today, these services are the
|
||||
only way to write a module. This RFC proposes a new design that simplifies the
|
||||
design and enables new use cases we are seeing today.
|
||||
|
||||
Taking a step back, we have seen the emergence of rollups and proving technologies.
|
||||
These technologies enable new use cases and new methods of achieving various goals.
|
||||
When we look at things like proving we look to [TinyGo](https://TinyGo.org/). When
|
||||
we have attempted to use TinyGo with existing modules we have run into a hiccup,
|
||||
the use of [gRPC](https://github.com/TinyGo-org/TinyGo/issues/2814) within modules.
|
||||
This has led us to look at a design which would allow the usage of TinyGo and
|
||||
other technologies.
|
||||
|
||||
We looked at TinyGo for our first target in order to compile down down to a 32 bit environment which could be used with
|
||||
things like [Risc-0](https://www.risczero.com/), [Fluent](https://fluentlabs.xyz/) and other technologies. When speaking with the teams behind these technologies
|
||||
we found that they were interested in using the Cosmos SDK but were unable to due to being unable to use TinyGo or the
|
||||
Cosmos SDK go code in a 32 bit environment.
|
||||
|
||||
The Cosmos SDK team has been hard at work over the last few months designing and implementing a modular core layer, with
|
||||
the idea that proving can be enabled later on. This design allows us to push the design of what can be done with the
|
||||
Cosmos SDK to the next level. In the future when we have proving tools and technologies integrated parts of the new core
|
||||
layer will be able to be used in conjunction with proving technologies without the need to rewrite the stack.
|
||||
|
||||
|
||||
## Proposal
|
||||
|
||||
This proposal is around enabling modules to be compiled to an environment in which they can be used with TinyGo and/or
|
||||
different proving technologies.
|
||||
|
||||
> Note the usage of handlers in modules is optional, modules can still use the existing design. This design is meant to
|
||||
> be a new way to write modules, with proving in mind, and is not meant to replace the existing design.
|
||||
|
||||
This proposal is for server/v2. Baseapp will continue to work in the same way as it does today.
|
||||
|
||||
### Pre and Post Message Handlers
|
||||
|
||||
In the Cosmos SDK, there exists hooks on messages and execution of function calls. Separating the two we will focus on
|
||||
message hooks. When a message is implemented it can be unknown if others will use the module and if a message will need
|
||||
hooks. When hooks are needed before or after a message, users are required to fork the module. This is not ideal as it
|
||||
leads to a lot of forks of modules and a lot of code duplication.
|
||||
|
||||
Pre and Post message handlers solve this issue. Where we allow modules to register listeners for messages in order to
|
||||
execute something before and/or after the message. Although hooks can be bypassed by doing keeper function calls, we can
|
||||
assume that as we shift the communication design of the SDK to use messages instead of keeper calls it will be safe to
|
||||
assume that the bypass surface of hooks will reduce to zero.
|
||||
|
||||
If an application developer would like to check the sender of funds before the message is executed they can
|
||||
register a pre message handler. If the message is called by a user the pre message handler will be called with the custom
|
||||
logic. If the sender is not allowed to send funds the pre-message handler can return an error and the message will not be
|
||||
executed.
|
||||
|
||||
> Note: This is different from the ante-handler and post-handler we have today. These will still exist in the same form.
|
||||
|
||||
A module can register handlers for any or all message(s), this allows for modules to be extended without the need to fork.
|
||||
|
||||
A module will implement the below for a pre-message hook:
|
||||
|
||||
```golang
|
||||
package core_appmodule
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"google.golang.org/protobuf/runtime/protoiface"
|
||||
)
|
||||
|
||||
type PreMsgHandlerRouter interface {
|
||||
// RegisterGlobalPreMsgHandler will register a pre msg handler that hooks before any message executes.
|
||||
// Handler will be called before ANY message executes.
|
||||
RegisterGlobalPreMsgHandler(handler func(ctx context.Context, msg protoiface.MessageV1) error)
|
||||
// RegisterPreMsgHandler will register a pre msg handler that hooks before the provided message
|
||||
// with the given message name executes. Handler will be called before the message is executed
|
||||
// by the module.
|
||||
RegisterPreMsgHandler(msgName string, handler func(ctx context.Context, msg protoiface.MessageV1) error)
|
||||
}
|
||||
|
||||
type HasPreMsgHandler interface {
|
||||
RegisterPreMsgHandler(router PreMsgHandlerRouter)
|
||||
}
|
||||
```
|
||||
|
||||
A module will implement the below for a postmessage hook:
|
||||
|
||||
```go
|
||||
package core_appmodule
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"google.golang.org/protobuf/runtime/protoiface"
|
||||
)
|
||||
|
||||
type PostMsgHandlerRouter interface {
|
||||
// RegisterGlobalPostMsgHandler will register a post msg handler that hooks after any message executes.
|
||||
// Handler will be called after ANY message executes, alongside the response.
|
||||
RegisterGlobalPostMsgHandler(handler func(ctx context.Context, msg, msgResp protoiface.MessageV1) error)
|
||||
// RegisterPostMsgHandler will register a pre msg handler that hooks after the provided message
|
||||
// with the given message name executes. Handler will be called after the message is executed
|
||||
// by the module, alongside the response returned by the module.
|
||||
RegisterPostMsgHandler(msgName string, handler func(ctx context.Context, msg, msgResp protoiface.MessageV1) error)
|
||||
}
|
||||
|
||||
type HasPostMsgHandler interface {
|
||||
RegisterPostMsgHandler(router PostMsgHandlerRouter)
|
||||
}
|
||||
```
|
||||
|
||||
We note the following behaviors:
|
||||
|
||||
* A pre msg handler returning an error will yield to a state transition revert.
|
||||
* A post msg handler returning an error will yield to a state transition revert.
|
||||
* A post msg handler will not be called if the execution handler (message handler) fails.
|
||||
* A post msg handler will be called only if the execution handler succeeds alongside the response provided by the execution handler.
|
||||
|
||||
### Message and Query Handlers
|
||||
|
||||
Similar to the above design, message handlers will allow the application developer to replace existing gRPC based services
|
||||
with handlers. This enables the module to be compiled down to TinyGo, and abandon the gRPC dependency. As mentioned
|
||||
upgrading the modules immediately is not mandatory, module developers can do so in a gradual way. Application developers have the option to use the existing gRPC services or the new handlers.
|
||||
|
||||
For message handlers we propose the introduction of the following core/appmodule interfaces and functions:
|
||||
|
||||
```go
|
||||
package core_appmodule
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"google.golang.org/protobuf/runtime/protoiface"
|
||||
)
|
||||
|
||||
type MsgHandlerRouter interface {
|
||||
RegisterMsgHandler(msgName string, handler func(ctx context.Context, msg protoiface.MessageV1) (msgResp protoiface.MessageV1, err error))
|
||||
}
|
||||
|
||||
type HasMsgHandler interface {
|
||||
RegisterMsgHandlers(router MsgHandlerRouter)
|
||||
}
|
||||
|
||||
// RegisterMsgHandler is a helper function to retain type safety when creating handlers, so we do not need to cast messages.
|
||||
func RegisterMsgHandler[Req, Resp protoiface.MessageV1](router MsgHandlerRouter, handler func(ctx context.Context, req Req) (resp Resp, err error)) {
|
||||
// impl detail
|
||||
}
|
||||
```
|
||||
|
||||
Example
|
||||
|
||||
```go
|
||||
package bank
|
||||
|
||||
func (b BankKeeper) Send(ctx context.Context, msg bank.MsgSend) (bank.MsgSendResponse, error) {
|
||||
// logic
|
||||
}
|
||||
|
||||
func (b BankModule) RegisterMsgHandlers(router core_appmodule.MsgHandlerRouter) {
|
||||
// the RegisterMsgHandler function takes care of doing type casting and conversions, ensuring we retain type safety
|
||||
core_appmodule.RegisterMsgHandler(router, b.Send)
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
This change is fully state machine compatible as, even if we were using gRPC, messages were
|
||||
routed using the message type name and not the gRCP method name.
|
||||
|
||||
We apply the same principles of MsgHandlers to QueryHandlers, by introducing a new core/appmodule interface:
|
||||
|
||||
```go
|
||||
package core_appmodule
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"google.golang.org/protobuf/runtime/protoiface"
|
||||
)
|
||||
|
||||
type QueryHandlerRouter interface {
|
||||
RegisterQueryHandler(msgName string, handler func(ctx context.Context, req protoiface.MessageV1) (resp protoiface.MessageV1, err error))
|
||||
}
|
||||
|
||||
type HasQueryHandler interface {
|
||||
RegisterQueryHandlers(router QueryHandlerRouter)
|
||||
}
|
||||
|
||||
// RegisterQueryHandler is a helper function to retain type safety when creating handlers, so we do not need to cast messages.
|
||||
func RegisterQueryHandler[Req, Resp protoiface.MessageV1](router QueryHandlerRouter, handler func(ctx context.Context, req Req) (resp Resp, err error)) {
|
||||
// impl detail
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
The difference between gRPC handlers and query handlers is that we expect query handlers to be deterministic and usable
|
||||
in consensus by other modules. Non consensus queries should be registered outside of the state machine itself, and we will
|
||||
provide guidelines on how to do so with serverv2.
|
||||
|
||||
As a consequence queries would be now mapped by their message name.
|
||||
|
||||
We can provide JSON exposure of the Query APIs following this rest API format:
|
||||
|
||||
```
|
||||
method: POST
|
||||
path: /msg_name
|
||||
ReqBody: protojson.Marshal(msg)
|
||||
----
|
||||
RespBody: protojson.Marshal(msgResp)
|
||||
```
|
||||
|
||||
### Consensus Messages
|
||||
|
||||
Similar to the above design, consensus messages will allow the underlying consensus engine to speak to the modules. Today we get consensus related information from `sdk.Context`. In server/v2 we are unable to continue with this design due to the forced dependency leakage of comet throughout the repo. Secondly, while we already have `cometInfo` if we were to put this on the new execution client we would be tieing CometBFT to the application manager and STF.
|
||||
|
||||
In the case of CometBFT, consensus would register handlers for consensus messages for evidence, voteinfo and consensus params. This would allow the consensus engine to speak to the modules.
|
||||
|
||||
|
||||
```go
|
||||
package consensus
|
||||
|
||||
func (b ConsensusKeeper) ConsensusParams(ctx context.Context, msg bank.MsgConsensusParams) (bank.MsgConsensusParamsResponse, error) {
|
||||
// logic
|
||||
}
|
||||
|
||||
func (b CircuitModule) RegisterConsensusHandlers(router core_appmodule.MsgHandlerRouter) {
|
||||
// the RegisterConsensusHandler function takes care of doing type casting and conversions, ensuring we retain type safety
|
||||
core_appmodule.RegisterConsensusHandler(router, b.Send)
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
|
||||
## Consequences
|
||||
|
||||
* REST endpoints for message and queries change due to lack of services and gRPC gatway annotations.
|
||||
* When using gRPC directly, one must query a schema endpoint in order to see all possible messages and queries.
|
||||
|
||||
### Backwards Compatibility
|
||||
|
||||
The way to interact with modules changes, REST and gRPC will still be available.
|
||||
|
||||
### Positive
|
||||
|
||||
* Allows modules to be compiled to TinyGo.
|
||||
* Reduces the cosmos-sdk's learning curve, since understanding gRPC semantics is not a must anymore.
|
||||
* Allows other modules to extend existing modules behaviour using pre and post msg handlers, without forking.
|
||||
* The system becomes overall more simple as gRPC is not anymore a hard dependency and requirement for the state machine.
|
||||
* Reduces the need on sdk.Context
|
||||
* Concurrently safe
|
||||
* Reduces public interface of modules
|
||||
|
||||
### Negative
|
||||
|
||||
* Pre, Post and Consensus msg handlers are a new concept that module developers need to learn (although not immediately).
|
||||
|
||||
### Neutral
|
||||
|
||||
> {neutral consequences}
|
||||
|
||||
### References
|
||||
|
||||
> Links to external materials needed to follow the discussion may be added here.
|
||||
>
|
||||
> In addition, if the discussion in a request for comments leads to any design
|
||||
> decisions, it may be helpful to add links to the ADR documents here after the
|
||||
> discussion has settled.
|
||||
|
||||
## Discussion
|
||||
|
||||
> This section contains the core of the discussion.
|
||||
>
|
||||
> There is no fixed format for this section, but ideally changes to this
|
||||
> section should be updated before merging to reflect any discussion that took
|
||||
> place on the PR that made those changes.
|
||||
Loading…
Reference in New Issue
Block a user