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

fix: find Cosmos SDK runtime app registered modules #3661

Merged
merged 7 commits into from
Sep 27, 2023
1 change: 1 addition & 0 deletions changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
- [#3610](https://github.com/ignite/cli/pull/3610) Fix overflow issue of cosmos faucet in `pkg/cosmosfaucet/transfer.go` and `pkg/cosmosfaucet/cosmosfaucet.go`
- [#3618](https://github.com/ignite/cli/pull/3618) Fix TS client generation import path issue
- [#3631](https://github.com/ignite/cli/pull/3631) Fix unnecessary vue import in hooks/composables template
- [#3661](https://github.com/ignite/cli/pull/3661) Change `pkg/cosmosanalysis` to find Cosmos SDK runtime app registered modules

## [`v0.27.0`](https://github.com/ignite/cli/releases/tag/v0.27.0)

Expand Down
148 changes: 145 additions & 3 deletions ignite/pkg/cosmosanalysis/app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,23 @@ import (
"go/format"
"go/parser"
"go/token"
"os"
"path/filepath"

"github.com/pkg/errors"

"github.com/ignite/cli/ignite/pkg/cosmosanalysis"
"github.com/ignite/cli/ignite/pkg/cosmosver"
"github.com/ignite/cli/ignite/pkg/goanalysis"
"github.com/ignite/cli/ignite/pkg/goenv"
"github.com/ignite/cli/ignite/pkg/gomodule"
"github.com/ignite/cli/ignite/pkg/xast"
)

const (
appWiringImport = "cosmossdk.io/depinject"
appWiringCallMethod = "Inject"
appWiringImport = "cosmossdk.io/depinject"
appWiringCallMethod = "Inject"
registerRoutesMethod = "RegisterAPIRoutes"
)

// CheckKeeper checks for the existence of the keeper with the provided name in the app structure.
Expand Down Expand Up @@ -96,6 +101,10 @@ func FindRegisteredModules(chainRoot string) (modules []string, err error) {
return nil, err
}

// The modules registered by Cosmos SDK `rumtime.App` are included
// when the app registers API modules though the `App` instance.
var includeRuntimeModules bool

// Loop on package's files
for _, f := range appPkg.Files {
fileImports := goanalysis.FormatImports(f)
Expand All @@ -118,6 +127,11 @@ func FindRegisteredModules(chainRoot string) (modules []string, err error) {
return xast.ErrStop
}

// Check if Cosmos SDK runtime App is called to register API routes
if !includeRuntimeModules {
includeRuntimeModules = checkRuntimeAppCalled(n)
}

// Find modules in RegisterAPIRoutes declaration
if pkgs := findRegisterAPIRoutesRegistrations(n); pkgs != nil {
for _, p := range pkgs {
Expand All @@ -127,6 +141,7 @@ func FindRegisteredModules(chainRoot string) (modules []string, err error) {
}
modules = append(modules, importModule)
}

return xast.ErrStop
}

Expand All @@ -136,6 +151,19 @@ func FindRegisteredModules(chainRoot string) (modules []string, err error) {
return nil, err
}
}

// Try to find the modules registered in Cosmos SDK `runtime.App`.
// This is required to properly generate OpenAPI specs for these
// modules when `app.App.RegisterAPIRoutes` is called.
if includeRuntimeModules {
runtimeModules, err := findRuntimeRegisteredModules(chainRoot)
if err != nil {
return nil, err
}

modules = append(modules, runtimeModules...)
}

return modules, nil
}

Expand Down Expand Up @@ -270,7 +298,7 @@ func findRegisterAPIRoutesRegistrations(n ast.Node) []string {
return nil
}

if funcLitType.Name.Name != "RegisterAPIRoutes" {
if funcLitType.Name.Name != registerRoutesMethod {
return nil
}

Expand Down Expand Up @@ -306,3 +334,117 @@ func findRegisterAPIRoutesRegistrations(n ast.Node) []string {

return packagesRegistered
}

func checkRuntimeAppCalled(n ast.Node) bool {
funcLitType, ok := n.(*ast.FuncDecl)
if !ok {
return false
}

if funcLitType.Name.Name != registerRoutesMethod {
return false
}

for _, stmt := range funcLitType.Body.List {
exprStmt, ok := stmt.(*ast.ExprStmt)
if !ok {
continue
}

exprCall, ok := exprStmt.X.(*ast.CallExpr)
if !ok {
continue
}

exprFun, ok := exprCall.Fun.(*ast.SelectorExpr)
if !ok || exprFun.Sel.Name != registerRoutesMethod {
continue
}

exprSel, ok := exprFun.X.(*ast.SelectorExpr)
if !ok || exprSel.Sel.Name != "App" {
continue
}

identType, ok := exprSel.X.(*ast.Ident)
if !ok || identType.Name != "app" {
continue
}

return true
}

return false
}

func findRuntimeRegisteredModules(chainRoot string) ([]string, error) {
// Resolve the absolute path to the Cosmos SDK module
cosmosPath, err := resolveCosmosPackagePath(chainRoot)
if err != nil {
return nil, err
}

var modules []string

// When runtime package doesn't exists it means is an older Cosmos SDK version,
// so all the module API registrations are defined within user's app.
path := filepath.Join(cosmosPath, "runtime", "app.go")
if _, err := os.Stat(path); os.IsNotExist(err) {
return modules, nil
}

f, _, err := xast.ParseFile(path)
if err != nil {
return nil, err
}

imports := goanalysis.FormatImports(f)
err = xast.Inspect(f, func(n ast.Node) error {
if pkgs := findRegisterAPIRoutesRegistrations(n); pkgs != nil {
for _, p := range pkgs {
if m := imports[p]; m != "" {
modules = append(modules, m)
}
}
return xast.ErrStop
}
return nil
})

if err != nil {
return nil, err
}
return modules, nil
}

func resolveCosmosPackagePath(chainRoot string) (string, error) {
modFile, err := gomodule.ParseAt(chainRoot)
if err != nil {
return "", err
}

deps, err := gomodule.ResolveDependencies(modFile)
if err != nil {
return "", err
}

var pkg string
for _, dep := range deps {
if dep.Path == cosmosver.CosmosModulePath {
pkg = dep.String()
break
}
}

if pkg == "" {
return "", errors.New("Cosmos SDK package version not found")
}

// Check path of the package directory within Go's module cache
path := filepath.Join(goenv.GoModCache(), pkg)
info, err := os.Stat(path)
if os.IsNotExist(err) || !info.IsDir() {
return "", errors.New("local path to Cosmos SDK package not found")
}
return path, nil
}
11 changes: 11 additions & 0 deletions ignite/pkg/cosmosanalysis/app/app_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,17 @@ func TestFindRegisteredModules(t *testing.T) {
"github.com/cosmos/cosmos-sdk/client/grpc/tmservice",
),
},
{
name: "with runtime api routes",
path: "testdata/modules/runtime_api_routes",
expectedModules: append(
basicModules,
"github.com/cosmos/cosmos-sdk/x/auth/tx",
"github.com/cosmos/cosmos-sdk/client/grpc/tmservice",
"github.com/username/test/x/foo",
"github.com/cosmos/cosmos-sdk/client/grpc/node",
),
},
{
name: "same file function",
path: "testdata/modules/file_function",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package app

import (
"cosmossdk.io/api/tendermint/abci"
"cosmossdk.io/client/v2/autocli"
"github.com/cosmos/cosmos-sdk/client"
codectypes "github.com/cosmos/cosmos-sdk/codec/types"
"github.com/cosmos/cosmos-sdk/runtime"
"github.com/cosmos/cosmos-sdk/server/api"
"github.com/cosmos/cosmos-sdk/server/config"
storetypes "github.com/cosmos/cosmos-sdk/store/types"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/types/module"
"github.com/cosmos/cosmos-sdk/x/auth"
"github.com/cosmos/cosmos-sdk/x/bank"
"github.com/cosmos/cosmos-sdk/x/gov"
govclient "github.com/cosmos/cosmos-sdk/x/gov/client"
paramsclient "github.com/cosmos/cosmos-sdk/x/params/client"
"github.com/cosmos/cosmos-sdk/x/staking"
foomodule "github.com/username/test/x/foo"
)

// App modules are defined as NewBasicManager arguments
var ModuleBasics = module.NewBasicManager(
auth.AppModuleBasic{},
bank.AppModuleBasic{},
staking.AppModuleBasic{},
gov.NewAppModuleBasic([]govclient.ProposalHandler{
paramsclient.ProposalHandler,
}),
foomodule.AppModuleBasic{},
)

type Foo struct {
*runtime.App
}

func (Foo) Name() string { return "foo" }
func (Foo) InterfaceRegistry() codectypes.InterfaceRegistry { return nil }
func (Foo) TxConfig() client.TxConfig { return nil }
func (Foo) AutoCliOpts() autocli.AppOptions { return autocli.AppOptions{} }

func (Foo) BeginBlocker(sdk.Context, abci.RequestBeginBlock) abci.ResponseBeginBlock {
return abci.ResponseBeginBlock{}
}

func (Foo) EndBlocker(sdk.Context, abci.RequestEndBlock) abci.ResponseEndBlock {
return abci.ResponseEndBlock{}
}

func (app *Foo) RegisterAPIRoutes(s *api.Server, cfg config.APIConfig) {
// This module should be discovered
foomodule.RegisterGRPCGatewayRoutes(s.ClientCtx, s.GRPCGatewayRouter)
// Runtime app modules for the current Cosmos SDK should be discovered too
app.App.RegisterAPIRoutes(apiSvr, apiConfig)
}

func (Foo) GetKey(storeKey string) *storetypes.KVStoreKey { return nil }

func (Foo) TxConfig() client.TxConfig { return nil }
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
module app

go 1.20

require (
cosmossdk.io/api v0.3.1
cosmossdk.io/core v0.5.1
cosmossdk.io/depinject v1.0.0-alpha.3
cosmossdk.io/errors v1.0.0-beta.7
cosmossdk.io/math v1.0.1
github.com/bufbuild/buf v1.23.1
github.com/cometbft/cometbft v0.37.2
github.com/cometbft/cometbft-db v0.8.0
github.com/cosmos/cosmos-proto v1.0.0-beta.2
github.com/cosmos/cosmos-sdk v0.47.3
github.com/cosmos/gogoproto v1.4.10
github.com/cosmos/ibc-go/v7 v7.2.0
github.com/golang/protobuf v1.5.3
github.com/gorilla/mux v1.8.0
github.com/grpc-ecosystem/grpc-gateway v1.16.0
github.com/grpc-ecosystem/grpc-gateway/v2 v2.15.2
github.com/spf13/cast v1.5.1
github.com/spf13/cobra v1.7.0
github.com/spf13/pflag v1.0.5
github.com/stretchr/testify v1.8.4
google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1
google.golang.org/grpc v1.55.0
google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0
google.golang.org/protobuf v1.31.0
)

replace (
// use cosmos fork of keyring
github.com/99designs/keyring => github.com/cosmos/keyring v1.2.0
// replace broken goleveldb
github.com/syndtr/goleveldb => github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7
)
15 changes: 15 additions & 0 deletions ignite/pkg/goenv/goenv.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,14 @@ const (

// GOPATH is the env var for GOPATH.
GOPATH = "GOPATH"

// GOMODCACHE is the env var for GOMODCACHE.
GOMODCACHE = "GOMODCACHE"
)

const (
binDir = "bin"
modDir = "pkg/mod"
)

// Bin returns the path of where Go binaries are installed.
Expand All @@ -40,3 +44,14 @@ func Path() string {
func ConfigurePath() error {
return os.Setenv("PATH", Path())
}

// GoModCache returns the path to Go's module cache.
func GoModCache() string {
if path := os.Getenv(GOMODCACHE); path != "" {
return path
}
if path := os.Getenv(GOPATH); path != "" {
return filepath.Join(path, modDir)
}
return filepath.Join(build.Default.GOPATH, modDir)
}
Loading
Loading