Add ADR 031 BaseApp and codec infrastructure (#7519)
* Refactor RegisterQueryServices -> RegisterServices * Cleaner proto files * Fix tests * Add MsgServer * Fix lint * Remove MsgServer from configurator for now * Remove useless file * Fix build * typo * Add router * Fix test * WIP * Add router * Remove test helper * Add beginning of test * Move test to simapp? * ServiceMsg implement sdk.Msg * Add handler by MsgServiceRouter * Correct signature * Add full test * use TxEncoder * Update baseapp/msg_service_router.go Co-authored-by: Aaron Craelius <aaron@regen.network> * Push changes * WIP on ServiceMsg unpacking * Make TestMsgService test pass * Fix tests * Tidying up * Tidying up * Tidying up * Add JSON test * Add comments * Tidying * Lint * Register MsgRequest interface * Rename * Fix tests * RegisterCustomTypeURL * Add changelog entries * Put in features * Update baseapp/msg_service_router.go Co-authored-by: Aleksandr Bezobchuk <alexanderbez@users.noreply.github.com> * Update baseapp/msg_service_router.go Co-authored-by: Aleksandr Bezobchuk <alexanderbez@users.noreply.github.com> * Update baseapp/msg_service_router.go Co-authored-by: Aleksandr Bezobchuk <alexanderbez@users.noreply.github.com> * Update baseapp/msg_service_router.go Co-authored-by: Aleksandr Bezobchuk <alexanderbez@users.noreply.github.com> * Update baseapp/msg_service_router.go Co-authored-by: Aleksandr Bezobchuk <alexanderbez@users.noreply.github.com> * Update baseapp/msg_service_router.go Co-authored-by: Aleksandr Bezobchuk <alexanderbez@users.noreply.github.com> * Update baseapp/msg_service_router.go Co-authored-by: Aleksandr Bezobchuk <alexanderbez@users.noreply.github.com> * Address review comments * Address nit * Fix lint * Update codec/types/interface_registry.go Co-authored-by: Marie Gauthier <marie.gauthier63@gmail.com> * godoc Co-authored-by: Aaron Craelius <aaronc@users.noreply.github.com> Co-authored-by: Aaron Craelius <aaron@regen.network> Co-authored-by: Aleksandr Bezobchuk <alexanderbez@users.noreply.github.com> Co-authored-by: Marie Gauthier <marie.gauthier63@gmail.com>
This commit is contained in:
co-authored by
Aaron Craelius
Aleksandr Bezobchuk
Marie Gauthier
Aaron Craelius
parent
e6e4a94071
commit
55242a659c
+1
-1
@@ -688,7 +688,7 @@ func handleQueryApp(app *BaseApp, path []string, req abci.RequestQuery) abci.Res
|
||||
Result: res,
|
||||
}
|
||||
|
||||
bz, err := codec.ProtoMarshalJSON(simRes)
|
||||
bz, err := codec.ProtoMarshalJSON(simRes, app.interfaceRegistry)
|
||||
if err != nil {
|
||||
return sdkerrors.QueryResult(sdkerrors.Wrap(err, "failed to JSON encode simulation response"))
|
||||
}
|
||||
|
||||
+51
-26
@@ -13,6 +13,7 @@ import (
|
||||
tmproto "github.com/tendermint/tendermint/proto/tendermint/types"
|
||||
dbm "github.com/tendermint/tm-db"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/codec/types"
|
||||
"github.com/cosmos/cosmos-sdk/snapshots"
|
||||
"github.com/cosmos/cosmos-sdk/store"
|
||||
"github.com/cosmos/cosmos-sdk/store/rootmulti"
|
||||
@@ -45,15 +46,17 @@ type (
|
||||
// BaseApp reflects the ABCI application implementation.
|
||||
type BaseApp struct { // nolint: maligned
|
||||
// initialized on creation
|
||||
logger log.Logger
|
||||
name string // application name from abci.Info
|
||||
db dbm.DB // common DB backend
|
||||
cms sdk.CommitMultiStore // Main (uncached) state
|
||||
storeLoader StoreLoader // function to handle store loading, may be overridden with SetStoreLoader()
|
||||
router sdk.Router // handle any kind of message
|
||||
queryRouter sdk.QueryRouter // router for redirecting query calls
|
||||
grpcQueryRouter *GRPCQueryRouter // router for redirecting gRPC query calls
|
||||
txDecoder sdk.TxDecoder // unmarshal []byte into sdk.Tx
|
||||
logger log.Logger
|
||||
name string // application name from abci.Info
|
||||
db dbm.DB // common DB backend
|
||||
cms sdk.CommitMultiStore // Main (uncached) state
|
||||
storeLoader StoreLoader // function to handle store loading, may be overridden with SetStoreLoader()
|
||||
router sdk.Router // handle any kind of message
|
||||
queryRouter sdk.QueryRouter // router for redirecting query calls
|
||||
grpcQueryRouter *GRPCQueryRouter // router for redirecting gRPC query calls
|
||||
msgServiceRouter *MsgServiceRouter // router for redirecting Msg service messages
|
||||
interfaceRegistry types.InterfaceRegistry
|
||||
txDecoder sdk.TxDecoder // unmarshal []byte into sdk.Tx
|
||||
|
||||
anteHandler sdk.AnteHandler // ante handler for fee and auth
|
||||
initChainer sdk.InitChainer // initialize state with validators and state blob
|
||||
@@ -136,16 +139,17 @@ func NewBaseApp(
|
||||
name string, logger log.Logger, db dbm.DB, txDecoder sdk.TxDecoder, options ...func(*BaseApp),
|
||||
) *BaseApp {
|
||||
app := &BaseApp{
|
||||
logger: logger,
|
||||
name: name,
|
||||
db: db,
|
||||
cms: store.NewCommitMultiStore(db),
|
||||
storeLoader: DefaultStoreLoader,
|
||||
router: NewRouter(),
|
||||
queryRouter: NewQueryRouter(),
|
||||
grpcQueryRouter: NewGRPCQueryRouter(),
|
||||
txDecoder: txDecoder,
|
||||
fauxMerkleMode: false,
|
||||
logger: logger,
|
||||
name: name,
|
||||
db: db,
|
||||
cms: store.NewCommitMultiStore(db),
|
||||
storeLoader: DefaultStoreLoader,
|
||||
router: NewRouter(),
|
||||
queryRouter: NewQueryRouter(),
|
||||
grpcQueryRouter: NewGRPCQueryRouter(),
|
||||
msgServiceRouter: NewMsgServiceRouter(),
|
||||
txDecoder: txDecoder,
|
||||
fauxMerkleMode: false,
|
||||
}
|
||||
|
||||
for _, option := range options {
|
||||
@@ -176,6 +180,9 @@ func (app *BaseApp) Logger() log.Logger {
|
||||
return app.logger
|
||||
}
|
||||
|
||||
// MsgServiceRouter returns the MsgServiceRouter of a BaseApp.
|
||||
func (app *BaseApp) MsgServiceRouter() *MsgServiceRouter { return app.msgServiceRouter }
|
||||
|
||||
// MountStores mounts all IAVL or DB stores to the provided keys in the BaseApp
|
||||
// multistore.
|
||||
func (app *BaseApp) MountStores(keys ...sdk.StoreKey) {
|
||||
@@ -682,20 +689,38 @@ func (app *BaseApp) runMsgs(ctx sdk.Context, msgs []sdk.Msg, mode runTxMode) (*s
|
||||
break
|
||||
}
|
||||
|
||||
msgRoute := msg.Route()
|
||||
handler := app.router.Route(ctx, msgRoute)
|
||||
var (
|
||||
msgEvents sdk.Events
|
||||
msgResult *sdk.Result
|
||||
msgFqName string
|
||||
err error
|
||||
)
|
||||
|
||||
if handler == nil {
|
||||
return nil, sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "unrecognized message route: %s; message index: %d", msgRoute, i)
|
||||
if svcMsg, ok := msg.(sdk.ServiceMsg); ok {
|
||||
msgFqName = svcMsg.MethodName
|
||||
handler := app.msgServiceRouter.Handler(msgFqName)
|
||||
if handler == nil {
|
||||
return nil, sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "unrecognized message service method: %s; message index: %d", msgFqName, i)
|
||||
}
|
||||
msgResult, err = handler(ctx, svcMsg.Request)
|
||||
} else {
|
||||
// legacy sdk.Msg routing
|
||||
msgRoute := msg.Route()
|
||||
msgFqName = msg.Type()
|
||||
handler := app.router.Route(ctx, msgRoute)
|
||||
if handler == nil {
|
||||
return nil, sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "unrecognized message route: %s; message index: %d", msgRoute, i)
|
||||
}
|
||||
|
||||
msgResult, err = handler(ctx, msg)
|
||||
}
|
||||
|
||||
msgResult, err := handler(ctx, msg)
|
||||
if err != nil {
|
||||
return nil, sdkerrors.Wrapf(err, "failed to execute message; message index: %d", i)
|
||||
}
|
||||
|
||||
msgEvents := sdk.Events{
|
||||
sdk.NewEvent(sdk.EventTypeMessage, sdk.NewAttribute(sdk.AttributeKeyAction, msg.Type())),
|
||||
msgEvents = sdk.Events{
|
||||
sdk.NewEvent(sdk.EventTypeMessage, sdk.NewAttribute(sdk.AttributeKeyAction, msgFqName)),
|
||||
}
|
||||
msgEvents = msgEvents.AppendEvents(msgResult.GetEvents())
|
||||
|
||||
|
||||
@@ -1692,9 +1692,9 @@ func TestQuery(t *testing.T) {
|
||||
|
||||
func TestGRPCQuery(t *testing.T) {
|
||||
grpcQueryOpt := func(bapp *BaseApp) {
|
||||
testdata.RegisterTestServiceServer(
|
||||
testdata.RegisterQueryServer(
|
||||
bapp.GRPCQueryRouter(),
|
||||
testdata.TestServiceImpl{},
|
||||
testdata.QueryImpl{},
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1711,7 +1711,7 @@ func TestGRPCQuery(t *testing.T) {
|
||||
|
||||
reqQuery := abci.RequestQuery{
|
||||
Data: reqBz,
|
||||
Path: "/testdata.TestService/SayHello",
|
||||
Path: "/testdata.Query/SayHello",
|
||||
}
|
||||
|
||||
resQuery := app.Query(reqQuery)
|
||||
|
||||
@@ -4,12 +4,11 @@ import (
|
||||
gocontext "context"
|
||||
"fmt"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/codec/types"
|
||||
|
||||
gogogrpc "github.com/gogo/protobuf/grpc"
|
||||
abci "github.com/tendermint/tendermint/abci/types"
|
||||
"google.golang.org/grpc"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/codec/types"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
)
|
||||
|
||||
@@ -22,6 +21,11 @@ type QueryServiceTestHelper struct {
|
||||
ctx sdk.Context
|
||||
}
|
||||
|
||||
var (
|
||||
_ gogogrpc.Server = &QueryServiceTestHelper{}
|
||||
_ gogogrpc.ClientConn = &QueryServiceTestHelper{}
|
||||
)
|
||||
|
||||
// NewQueryServerTestHelper creates a new QueryServiceTestHelper that wraps
|
||||
// the provided sdk.Context
|
||||
func NewQueryServerTestHelper(ctx sdk.Context, interfaceRegistry types.InterfaceRegistry) *QueryServiceTestHelper {
|
||||
@@ -62,6 +66,3 @@ func (q *QueryServiceTestHelper) Invoke(_ gocontext.Context, method string, args
|
||||
func (q *QueryServiceTestHelper) NewStream(gocontext.Context, *grpc.StreamDesc, string, ...grpc.CallOption) (grpc.ClientStream, error) {
|
||||
return nil, fmt.Errorf("not supported")
|
||||
}
|
||||
|
||||
var _ gogogrpc.Server = &QueryServiceTestHelper{}
|
||||
var _ gogogrpc.ClientConn = &QueryServiceTestHelper{}
|
||||
|
||||
@@ -15,12 +15,12 @@ func TestGRPCRouter(t *testing.T) {
|
||||
qr := NewGRPCQueryRouter()
|
||||
interfaceRegistry := testdata.NewTestInterfaceRegistry()
|
||||
qr.SetInterfaceRegistry(interfaceRegistry)
|
||||
testdata.RegisterTestServiceServer(qr, testdata.TestServiceImpl{})
|
||||
testdata.RegisterQueryServer(qr, testdata.QueryImpl{})
|
||||
helper := &QueryServiceTestHelper{
|
||||
GRPCQueryRouter: qr,
|
||||
ctx: sdk.Context{}.WithContext(context.Background()),
|
||||
}
|
||||
client := testdata.NewTestServiceClient(helper)
|
||||
client := testdata.NewQueryClient(helper)
|
||||
|
||||
res, err := client.Echo(context.Background(), &testdata.EchoRequest{Message: "hello"})
|
||||
require.Nil(t, err)
|
||||
|
||||
@@ -0,0 +1,94 @@
|
||||
package baseapp
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/gogo/protobuf/proto"
|
||||
|
||||
gogogrpc "github.com/gogo/protobuf/grpc"
|
||||
"google.golang.org/grpc"
|
||||
|
||||
codectypes "github.com/cosmos/cosmos-sdk/codec/types"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
)
|
||||
|
||||
// MsgServiceRouter routes fully-qualified Msg service methods to their handler.
|
||||
type MsgServiceRouter struct {
|
||||
interfaceRegistry codectypes.InterfaceRegistry
|
||||
routes map[string]MsgServiceHandler
|
||||
}
|
||||
|
||||
var _ gogogrpc.Server = &MsgServiceRouter{}
|
||||
|
||||
// NewMsgServiceRouter creates a new MsgServiceRouter.
|
||||
func NewMsgServiceRouter() *MsgServiceRouter {
|
||||
return &MsgServiceRouter{
|
||||
routes: map[string]MsgServiceHandler{},
|
||||
}
|
||||
}
|
||||
|
||||
// MsgServiceHandler defines a function type which handles Msg service message.
|
||||
type MsgServiceHandler = func(ctx sdk.Context, req sdk.MsgRequest) (*sdk.Result, error)
|
||||
|
||||
// Handler returns the MsgServiceHandler for a given query route path or nil
|
||||
// if not found.
|
||||
func (msr *MsgServiceRouter) Handler(methodName string) MsgServiceHandler {
|
||||
return msr.routes[methodName]
|
||||
}
|
||||
|
||||
// RegisterService implements the gRPC Server.RegisterService method. sd is a gRPC
|
||||
// service description, handler is an object which implements that gRPC service.
|
||||
func (msr *MsgServiceRouter) RegisterService(sd *grpc.ServiceDesc, handler interface{}) {
|
||||
// Adds a top-level query handler based on the gRPC service name.
|
||||
for _, method := range sd.Methods {
|
||||
fqMethod := fmt.Sprintf("/%s/%s", sd.ServiceName, method.MethodName)
|
||||
methodHandler := method.Handler
|
||||
|
||||
// NOTE: This is how we pull the concrete request type for each handler for registering in the InterfaceRegistry.
|
||||
// This approach is maybe a bit hacky, but less hacky than reflecting on the handler object itself.
|
||||
// We use a no-op interceptor to avoid actually calling into the handler itself.
|
||||
_, _ = methodHandler(nil, context.Background(), func(i interface{}) error {
|
||||
msg, ok := i.(proto.Message)
|
||||
if !ok {
|
||||
// We panic here because there is no other alternative and the app cannot be initialized correctly
|
||||
// this should only happen if there is a problem with code generation in which case the app won't
|
||||
// work correctly anyway.
|
||||
panic(fmt.Errorf("can't register request type %T for service method %s", i, fqMethod))
|
||||
}
|
||||
|
||||
msr.interfaceRegistry.RegisterCustomTypeURL((*sdk.MsgRequest)(nil), fqMethod, msg)
|
||||
return nil
|
||||
}, func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp interface{}, err error) {
|
||||
return nil, nil
|
||||
})
|
||||
|
||||
msr.routes[fqMethod] = func(ctx sdk.Context, req sdk.MsgRequest) (*sdk.Result, error) {
|
||||
ctx = ctx.WithEventManager(sdk.NewEventManager())
|
||||
|
||||
// Call the method handler from the service description with the handler object.
|
||||
res, err := methodHandler(handler, sdk.WrapSDKContext(ctx), func(_ interface{}) error {
|
||||
// We don't do any decoding here because the decoding was already done.
|
||||
return nil
|
||||
}, func(goCtx context.Context, _ interface{}, _ *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
|
||||
goCtx = context.WithValue(goCtx, sdk.SdkContextKey, ctx)
|
||||
return handler(goCtx, req)
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resMsg, ok := res.(proto.Message)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("can't proto encode %T", resMsg)
|
||||
}
|
||||
|
||||
return sdk.WrapServiceResult(ctx, resMsg, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// SetInterfaceRegistry sets the interface registry for the router.
|
||||
func (msr *MsgServiceRouter) SetInterfaceRegistry(interfaceRegistry codectypes.InterfaceRegistry) {
|
||||
msr.interfaceRegistry = interfaceRegistry
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
package baseapp_test
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/baseapp"
|
||||
|
||||
tmproto "github.com/tendermint/tendermint/proto/tendermint/types"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
abci "github.com/tendermint/tendermint/abci/types"
|
||||
"github.com/tendermint/tendermint/libs/log"
|
||||
dbm "github.com/tendermint/tm-db"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/client/tx"
|
||||
"github.com/cosmos/cosmos-sdk/simapp"
|
||||
"github.com/cosmos/cosmos-sdk/testutil/testdata"
|
||||
"github.com/cosmos/cosmos-sdk/types/tx/signing"
|
||||
authsigning "github.com/cosmos/cosmos-sdk/x/auth/signing"
|
||||
)
|
||||
|
||||
func TestMsgService(t *testing.T) {
|
||||
priv, _, _ := testdata.KeyTestPubAddr()
|
||||
encCfg := simapp.MakeEncodingConfig()
|
||||
db := dbm.NewMemDB()
|
||||
app := baseapp.NewBaseApp("test", log.NewTMLogger(log.NewSyncWriter(os.Stdout)), db, encCfg.TxConfig.TxDecoder())
|
||||
app.SetInterfaceRegistry(encCfg.InterfaceRegistry)
|
||||
testdata.RegisterMsgServer(
|
||||
app.MsgServiceRouter(),
|
||||
testdata.MsgServerImpl{},
|
||||
)
|
||||
_ = app.BeginBlock(abci.RequestBeginBlock{Header: tmproto.Header{Height: 1}})
|
||||
|
||||
msg := testdata.NewServiceMsgCreateDog(&testdata.MsgCreateDog{Dog: &testdata.Dog{Name: "Spot"}})
|
||||
txBuilder := encCfg.TxConfig.NewTxBuilder()
|
||||
txBuilder.SetFeeAmount(testdata.NewTestFeeAmount())
|
||||
txBuilder.SetGasLimit(testdata.NewTestGasLimit())
|
||||
err := txBuilder.SetMsgs(msg)
|
||||
require.NoError(t, err)
|
||||
|
||||
// First round: we gather all the signer infos. We use the "set empty
|
||||
// signature" hack to do that.
|
||||
sigV2 := signing.SignatureV2{
|
||||
PubKey: priv.PubKey(),
|
||||
Data: &signing.SingleSignatureData{
|
||||
SignMode: encCfg.TxConfig.SignModeHandler().DefaultMode(),
|
||||
Signature: nil,
|
||||
},
|
||||
Sequence: 0,
|
||||
}
|
||||
|
||||
err = txBuilder.SetSignatures(sigV2)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Second round: all signer infos are set, so each signer can sign.
|
||||
signerData := authsigning.SignerData{
|
||||
ChainID: "test",
|
||||
AccountNumber: 0,
|
||||
Sequence: 0,
|
||||
}
|
||||
sigV2, err = tx.SignWithPrivKey(
|
||||
encCfg.TxConfig.SignModeHandler().DefaultMode(), signerData,
|
||||
txBuilder, priv, encCfg.TxConfig, 0)
|
||||
require.NoError(t, err)
|
||||
err = txBuilder.SetSignatures(sigV2)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Send the tx to the app
|
||||
txBytes, err := encCfg.TxConfig.TxEncoder()(txBuilder.GetTx())
|
||||
require.NoError(t, err)
|
||||
res := app.DeliverTx(abci.RequestDeliverTx{Tx: txBytes})
|
||||
require.Equal(t, abci.CodeTypeOK, res.Code, "res=%+v", res)
|
||||
}
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
|
||||
dbm "github.com/tendermint/tm-db"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/codec/types"
|
||||
"github.com/cosmos/cosmos-sdk/snapshots"
|
||||
"github.com/cosmos/cosmos-sdk/store"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
@@ -221,3 +222,10 @@ func (app *BaseApp) SetSnapshotKeepRecent(snapshotKeepRecent uint32) {
|
||||
}
|
||||
app.snapshotKeepRecent = snapshotKeepRecent
|
||||
}
|
||||
|
||||
// SetInterfaceRegistry sets the InterfaceRegistry.
|
||||
func (app *BaseApp) SetInterfaceRegistry(registry types.InterfaceRegistry) {
|
||||
app.interfaceRegistry = registry
|
||||
app.grpcQueryRouter.SetInterfaceRegistry(registry)
|
||||
app.msgServiceRouter.SetInterfaceRegistry(registry)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user