Skip to content
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

test: v2 services helpers and demo using x/bank #23057

Open
wants to merge 33 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 23 commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
dab4f61
header service
aljo242 Dec 20, 2024
ccdbad4
update
aljo242 Dec 20, 2024
ef438eb
basic env
aljo242 Dec 20, 2024
9dfae05
transaction service
aljo242 Dec 20, 2024
779bd84
refit
aljo242 Dec 20, 2024
2e7ca6d
register
aljo242 Dec 20, 2024
6161b6a
utd
aljo242 Dec 20, 2024
7355259
Merge branch 'main' into feat/services-helpers
aljo242 Dec 20, 2024
46a30d3
bank
aljo242 Dec 20, 2024
89e5b26
no more sdk.context
aljo242 Dec 20, 2024
05a8577
working
aljo242 Dec 20, 2024
f827e73
lint fixes
aljo242 Dec 20, 2024
6c7dc2a
format
aljo242 Dec 20, 2024
ed63aca
Merge branch 'main' into feat/services-helpers
aljo242 Jan 3, 2025
c8b7460
Merge branch 'main' into feat/services-helpers
aljo242 Jan 3, 2025
4956083
lint
aljo242 Jan 3, 2025
b5703d4
Merge branch 'main' into feat/services-helpers
aljo242 Jan 3, 2025
c7bc69d
tidy all
aljo242 Jan 3, 2025
1442dc2
Merge branch 'main' into feat/services-helpers
aljo242 Jan 6, 2025
9d98953
Merge branch 'main' into feat/services-helpers
aljo242 Jan 6, 2025
d29ed65
Merge branch 'main' into feat/services-helpers
aljo242 Jan 6, 2025
d42ac1f
clean
aljo242 Jan 6, 2025
a6ffa04
use mocked query client
aljo242 Jan 6, 2025
fb55664
clean up
aljo242 Jan 6, 2025
87fb4f9
lint
aljo242 Jan 6, 2025
e18d809
server check
aljo242 Jan 6, 2025
4f56126
Merge branch 'main' into feat/services-helpers
aljo242 Jan 7, 2025
f67b33f
lint-fix
aljo242 Jan 7, 2025
997bf85
Merge branch 'main' into feat/services-helpers
aljo242 Jan 7, 2025
ed61ace
Merge branch 'main' into feat/services-helpers
aljo242 Jan 7, 2025
47a681e
init test
aljo242 Jan 8, 2025
9f54324
rm
aljo242 Jan 8, 2025
0cea6dd
Merge branch 'main' into feat/services-helpers
aljo242 Jan 8, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 53 additions & 3 deletions core/testing/context.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,65 @@ import (
"context"

"cosmossdk.io/core/event"
"cosmossdk.io/core/gas"
"cosmossdk.io/core/header"
"cosmossdk.io/core/store"
"cosmossdk.io/core/transaction"
)

type dummyKey struct{}

func Context() context.Context {
var _ context.Context = &TestContext{}

type TestContext struct {
context.Context
}

func Context() TestContext {
dummy := &dummyCtx{
stores: map[string]store.KVStore{},
events: map[string][]event.Event{},
protoEvents: map[string][]transaction.Msg{},
header: header.Info{},
execMode: transaction.ExecModeFinalize,
gasConfig: gas.GasConfig{},
gasMeter: nil,
}

return TestContext{
Context: context.WithValue(context.Background(), dummyKey{}, dummy),
}
}

// WithHeaderInfo sets the header on a testing ctx and returns the updated ctx.
func (t TestContext) WithHeaderInfo(info header.Info) TestContext {
dummy := unwrap(t.Context)
dummy.header = info

return TestContext{
Context: context.WithValue(t.Context, dummyKey{}, dummy),
}
}

// WithExecMode sets the exec mode on a testing ctx and returns the updated ctx.
func (t TestContext) WithExecMode(mode transaction.ExecMode) TestContext {
dummy := unwrap(t.Context)
dummy.execMode = mode

ctx := context.WithValue(context.Background(), dummyKey{}, dummy)
return ctx
return TestContext{
Context: context.WithValue(t.Context, dummyKey{}, dummy),
}
}

// WithGas sets the gas config and meter on a testing ctx and returns the updated ctx.
func (t TestContext) WithGas(gasConfig gas.GasConfig, gasMeter gas.Meter) TestContext {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Created the TestContext wrapper so that we can extend with methods to modify it like the ones here

dummy := unwrap(t.Context)
dummy.gasConfig = gasConfig
dummy.gasMeter = gasMeter

return TestContext{
Context: context.WithValue(t.Context, dummyKey{}, dummy),
}
}

type dummyCtx struct {
Expand All @@ -28,6 +72,12 @@ type dummyCtx struct {
events map[string][]event.Event
// maps proto events emitted by the actor.
protoEvents map[string][]transaction.Msg

header header.Info
execMode transaction.ExecMode

gasMeter gas.Meter
gasConfig gas.GasConfig
}

func unwrap(ctx context.Context) *dummyCtx {
Expand Down
65 changes: 65 additions & 0 deletions core/testing/environment.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package coretesting

import (
"context"

appmodulev2 "cosmossdk.io/core/appmodule/v2"
corecontext "cosmossdk.io/core/context"
corelog "cosmossdk.io/core/log"
"cosmossdk.io/core/router"
"cosmossdk.io/core/store"
)

type TestEnvironmentConfig struct {
ModuleName string
Logger corelog.Logger
MsgRouter router.Service
QueryRouter router.Service
}

type TestEnvironment struct {
appmodulev2.Environment

memEventsService MemEventsService
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Include the concrete types of our Mem services so that we can use the additional helper methods (such as GetEvents) in our tests

memHeaderService MemHeaderService
}

func NewTestEnvironment(cfg TestEnvironmentConfig) (TestContext, TestEnvironment) {
ctx := Context()

memEventService := EventsService(ctx, cfg.ModuleName)
memHeaderService := MemHeaderService{}

env := TestEnvironment{
Environment: appmodulev2.Environment{
Logger: cfg.Logger,
BranchService: nil,
EventService: memEventService,
GasService: MemGasService{},
HeaderService: memHeaderService,
QueryRouterService: cfg.QueryRouter,
MsgRouterService: cfg.MsgRouter,
TransactionService: MemTransactionService{},
KVStoreService: KVStoreService(ctx, cfg.ModuleName),
MemStoreService: nil,
},
memEventsService: memEventService,
memHeaderService: memHeaderService,
}

// set internal context to point to environment
ctx.Context = context.WithValue(ctx.Context, corecontext.EnvironmentContextKey, env.Environment)
return ctx, env
}

func (env TestEnvironment) MemEventsService() MemEventsService {
return env.memEventsService
}

func (env TestEnvironment) KVStoreService() store.KVStoreService {
return env.Environment.KVStoreService
}

func (env TestEnvironment) HeaderService() MemHeaderService {
return env.memHeaderService
}
23 changes: 23 additions & 0 deletions core/testing/gas.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package coretesting

import (
"context"

"cosmossdk.io/core/gas"
)

var _ gas.Service = &MemGasService{}

type MemGasService struct{}
technicallyty marked this conversation as resolved.
Show resolved Hide resolved

func (m MemGasService) GasMeter(ctx context.Context) gas.Meter {
dummy := unwrap(ctx)

return dummy.gasMeter
}

func (m MemGasService) GasConfig(ctx context.Context) gas.GasConfig {
dummy := unwrap(ctx)

return dummy.gasConfig
}
15 changes: 15 additions & 0 deletions core/testing/header.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package coretesting

import (
"context"

"cosmossdk.io/core/header"
)

var _ header.Service = &MemHeaderService{}

type MemHeaderService struct{}
technicallyty marked this conversation as resolved.
Show resolved Hide resolved

func (e MemHeaderService) HeaderInfo(ctx context.Context) header.Info {
return unwrap(ctx).header
}
159 changes: 159 additions & 0 deletions core/testing/router.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
package coretesting

import (
"context"
"errors"
"fmt"
"reflect"
Fixed Show fixed Hide fixed

appmodulev2 "cosmossdk.io/core/appmodule/v2"
"cosmossdk.io/core/router"
"cosmossdk.io/core/transaction"
)

var ErrNoHandler = errors.New("no handler")

// NewMsgRouterBuilder is a router that routes messages to their respective handlers.
func NewMsgRouterBuilder() *ReflectionRouterBuilder {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Core-only router builder that uses reflect instead of protoreflect for test pathfinding

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

my IDE says there are no usages of this function - is there an example usage of this somewhere?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This would be used for an appmodulev2 module - which this task doesn't touch

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

adding comment here from our conversation so we don't forget: resolution here is to add a test that utilizes this code in some way

return &ReflectionRouterBuilder{
handlers: make(map[string]appmodulev2.HandlerFunc),
preHandlers: make(map[string][]appmodulev2.PreMsgHandler),
postHandlers: make(map[string][]appmodulev2.PostMsgHandler),
}
}

type ReflectionRouterBuilder struct {
handlers map[string]appmodulev2.HandlerFunc
globalPreHandlers []appmodulev2.PreMsgHandler
preHandlers map[string][]appmodulev2.PreMsgHandler
postHandlers map[string][]appmodulev2.PostMsgHandler
globalPostHandlers []appmodulev2.PostMsgHandler
}

func (b *ReflectionRouterBuilder) RegisterHandler(msgType string, handler appmodulev2.HandlerFunc) error {
// panic on override
technicallyty marked this conversation as resolved.
Show resolved Hide resolved
if _, ok := b.handlers[msgType]; ok {
return fmt.Errorf("handler already registered: %s", msgType)
}
b.handlers[msgType] = handler
return nil
}

func (b *ReflectionRouterBuilder) RegisterGlobalPreMsgHandler(handler appmodulev2.PreMsgHandler) {
b.globalPreHandlers = append(b.globalPreHandlers, handler)
}

func (b *ReflectionRouterBuilder) RegisterPreMsgHandler(msgType string, handler appmodulev2.PreMsgHandler) {
b.preHandlers[msgType] = append(b.preHandlers[msgType], handler)
}

func (b *ReflectionRouterBuilder) RegisterPostMsgHandler(msgType string, handler appmodulev2.PostMsgHandler) {
b.postHandlers[msgType] = append(b.postHandlers[msgType], handler)
}

func (b *ReflectionRouterBuilder) RegisterGlobalPostMsgHandler(handler appmodulev2.PostMsgHandler) {
b.globalPostHandlers = append(b.globalPostHandlers, handler)
}

func (b *ReflectionRouterBuilder) HandlerExists(msgType string) bool {
_, ok := b.handlers[msgType]
return ok
}

func (b *ReflectionRouterBuilder) Build() (ReflectionRouter, error) {
handlers := make(map[string]appmodulev2.HandlerFunc)

globalPreHandler := func(ctx context.Context, msg transaction.Msg) error {
for _, h := range b.globalPreHandlers {
err := h(ctx, msg)
if err != nil {
return err
}
}
return nil
}

globalPostHandler := func(ctx context.Context, msg, msgResp transaction.Msg) error {
for _, h := range b.globalPostHandlers {
err := h(ctx, msg, msgResp)
if err != nil {
return err
}
}
return nil
}

for msgType, handler := range b.handlers {
// find pre handler
preHandlers := b.preHandlers[msgType]
// find post handler
postHandlers := b.postHandlers[msgType]
// build the handler
handlers[msgType] = buildHandler(handler, preHandlers, globalPreHandler, postHandlers, globalPostHandler)
}
Fixed Show fixed Hide fixed

return ReflectionRouter{
handlers: handlers,
}, nil
}

func buildHandler(
handler appmodulev2.HandlerFunc,
preHandlers []appmodulev2.PreMsgHandler,
globalPreHandler appmodulev2.PreMsgHandler,
postHandlers []appmodulev2.PostMsgHandler,
globalPostHandler appmodulev2.PostMsgHandler,
) appmodulev2.HandlerFunc {
return func(ctx context.Context, msg transaction.Msg) (msgResp transaction.Msg, err error) {
if len(preHandlers) != 0 {
for _, preHandler := range preHandlers {
if err := preHandler(ctx, msg); err != nil {
return nil, err
}
}
}
err = globalPreHandler(ctx, msg)
if err != nil {
return nil, err
}
msgResp, err = handler(ctx, msg)
if err != nil {
return nil, err
}

if len(postHandlers) != 0 {
for _, postHandler := range postHandlers {
if err := postHandler(ctx, msg, msgResp); err != nil {
return nil, err
}
}
}
err = globalPostHandler(ctx, msg, msgResp)
return msgResp, err
}
}

var _ router.Service = (*ReflectionRouter)(nil)

// ReflectionRouter implements the STF router for msg and query handlers.
type ReflectionRouter struct {
handlers map[string]appmodulev2.HandlerFunc
}

func (r ReflectionRouter) CanInvoke(_ context.Context, typeURL string) error {
_, exists := r.handlers[typeURL]
if !exists {
return fmt.Errorf("%w: %s", ErrNoHandler, typeURL)
}
return nil
}

func (r ReflectionRouter) Invoke(ctx context.Context, req transaction.Msg) (res transaction.Msg, err error) {
typeName := reflect.TypeOf(req).String()
handler, exists := r.handlers[typeName]
if !exists {
return nil, fmt.Errorf("%w: %s", ErrNoHandler, typeName)
}

return handler(ctx, req)
}
10 changes: 8 additions & 2 deletions core/testing/services_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,14 @@ import (
)

func TestKVStoreService(t *testing.T) {
ctx := Context()
svc1 := KVStoreService(ctx, "bank")
cfg := TestEnvironmentConfig{
ModuleName: "bank",
Logger: nil,
MsgRouter: nil,
QueryRouter: nil,
}
ctx, env := NewTestEnvironment(cfg)
svc1 := env.KVStoreService()

// must panic
t.Run("must panic on invalid ctx", func(t *testing.T) {
Expand Down
17 changes: 17 additions & 0 deletions core/testing/transaction.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package coretesting

import (
"context"

"cosmossdk.io/core/transaction"
)

var _ transaction.Service = &MemTransactionService{}

type MemTransactionService struct{}
technicallyty marked this conversation as resolved.
Show resolved Hide resolved

func (m MemTransactionService) ExecMode(ctx context.Context) transaction.ExecMode {
dummy := unwrap(ctx)

return dummy.execMode
}
2 changes: 1 addition & 1 deletion schema/appdata/mux.go
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@ func ListenerMux(listeners ...Listener) Listener {

mux.onBatch = func(batch PacketBatch) error {
for _, listener := range listeners {
err := batch.apply(&listener) //nolint:gosec // aliasing is safe here
err := batch.apply(&listener)
if err != nil {
return err
}
Expand Down
Loading
Loading