-
Notifications
You must be signed in to change notification settings - Fork 3.8k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add ADR 031 BaseApp and codec infrastructure #7519
Changes from 45 commits
64c76d2
d9b8a1d
889025b
b3c301d
e1482f2
6c77f06
c746a2b
ac713d2
59ebc3b
f18f4e3
1ed29df
0d9efc4
be1a26b
b35ce5d
fef78cf
ff05343
c323889
13b8dbd
ab00ebf
797ddd3
a65ab55
759b2a5
931ca66
f21b332
24fa6f0
12cbcec
b599b65
0c5ce17
14619c4
687fc09
b6bec3a
d89f2f6
82e2521
42f59ff
9a038a5
8c56444
a6a8132
aa3192f
55577f6
701f879
84033e4
3762e1d
b30a5eb
715d002
791097a
62a887f
98fa5d2
c63105d
2be1a61
105b04a
c306dff
eaebd7e
7cbc9f9
dfb6cf6
8624ad7
880c7cb
304e937
ca9258c
d6120af
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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,36 @@ 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 | ||
var msgResult *sdk.Result | ||
var err error | ||
var msgType string | ||
amaury1093 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
if svcMsg, ok := msg.(sdk.ServiceMsg); ok { | ||
msgType = svcMsg.MethodName | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Field is called MethodName yet the var is called msgType. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Renamed to |
||
handler := app.msgServiceRouter.Handler(msgType) | ||
if handler == nil { | ||
return nil, sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "unrecognized message service method: %s; message index: %d", msgType, i) | ||
} | ||
msgResult, err = handler(ctx, svcMsg.Request) | ||
} else { | ||
// legacy sdk.Msg routing | ||
msgRoute := msg.Route() | ||
msgType = 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) | ||
} | ||
|
||
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, msgType)), | ||
} | ||
msgEvents = msgEvents.AppendEvents(msgResult.GetEvents()) | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,97 @@ | ||
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 { | ||
handler, found := msr.routes[methodName] | ||
if !found { | ||
return nil | ||
} | ||
|
||
return handler | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why not just return There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
even if a key exists w/o a handler, then |
||
} | ||
|
||
// 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. | ||
amaury1093 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
// 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. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nice |
||
_, _ = 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 | ||
amaury1093 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
// this should only happen if there is a problem with code generation in which case the app won't | ||
// work correctly anyway | ||
amaury1093 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
panic(fmt.Errorf("can't register request type %T for service method %s", i, fqMethod)) | ||
} | ||
msr.interfaceRegistry.RegisterCustomTypeURL((*sdk.MsgRequest)(nil), fqMethod, msg) | ||
amaury1093 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
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(i interface{}) error { | ||
amaury1093 marked this conversation as resolved.
Show resolved
Hide resolved
amaury1093 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
// we don't do any decoding here because the decoding was already done | ||
return nil | ||
}, func(goCtx context.Context, _ interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) { | ||
amaury1093 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
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) | ||
amaury1093 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
} | ||
} | ||
|
||
// SetInterfaceRegistry sets the interface registry for the router. | ||
func (msr *MsgServiceRouter) SetInterfaceRegistry(interfaceRegistry codectypes.InterfaceRegistry) { | ||
msr.interfaceRegistry = interfaceRegistry | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
not introduced in this PR, but in #7518 (comment)