|  | 
|  | 1 | +package msgs | 
|  | 2 | + | 
|  | 3 | +import ( | 
|  | 4 | +	"go/ast" | 
|  | 5 | +	"go/parser" | 
|  | 6 | +	"go/token" | 
|  | 7 | +	"path/filepath" | 
|  | 8 | +	"strings" | 
|  | 9 | + | 
|  | 10 | +	"github.com/tendermint/starport/starport/pkg/gomodule" | 
|  | 11 | +	"github.com/tendermint/starport/starport/pkg/protoanalysis" | 
|  | 12 | +) | 
|  | 13 | + | 
|  | 14 | +// requirements holds a list of sdk.Msg's method names. | 
|  | 15 | +type requirements map[string]bool | 
|  | 16 | + | 
|  | 17 | +func newRequirements() requirements { | 
|  | 18 | +	return requirements{ | 
|  | 19 | +		"Reset":         false, | 
|  | 20 | +		"String":        false, | 
|  | 21 | +		"ProtoMessage":  false, | 
|  | 22 | +		"Route":         false, | 
|  | 23 | +		"Type":          false, | 
|  | 24 | +		"GetSigners":    false, | 
|  | 25 | +		"GetSignBytes":  false, | 
|  | 26 | +		"ValidateBasic": false, | 
|  | 27 | +	} | 
|  | 28 | +} | 
|  | 29 | + | 
|  | 30 | +// Msgs is a module import path-sdk msgs pair. | 
|  | 31 | +type Msgs map[string][]string | 
|  | 32 | + | 
|  | 33 | +// Discover discovers and returns pairs of module import path and their types that implements sdk.Msg. | 
|  | 34 | +// sourcePath is the root path of an sdk blockchain. | 
|  | 35 | +// | 
|  | 36 | +// discovery algorithm make use of proto definitions to discover modules inside the blockchain. | 
|  | 37 | +// | 
|  | 38 | +// checking whether a type implements sdk.Msg is done by running a simple algorithm of comparing method names | 
|  | 39 | +// of each type in a package with sdk.Msg's, which satisfies our needs for the time being. | 
|  | 40 | +// for a more opinionated check, go/types.Implements() might be utilized as needed. | 
|  | 41 | +func Discover(sourcePath string) (Msgs, error) { | 
|  | 42 | +	// find out base Go import path of the blockchain. | 
|  | 43 | +	gm, err := gomodule.ParseAt(sourcePath) | 
|  | 44 | +	if err != nil { | 
|  | 45 | +		return nil, err | 
|  | 46 | +	} | 
|  | 47 | +	bpath := gm.Module.Mod.Path | 
|  | 48 | + | 
|  | 49 | +	// find proto packages that belongs to modules under x/. | 
|  | 50 | +	xprotopkgs, err := findModuleProtoPkgs(sourcePath, bpath) | 
|  | 51 | +	if err != nil { | 
|  | 52 | +		return nil, err | 
|  | 53 | +	} | 
|  | 54 | + | 
|  | 55 | +	msgs := make(Msgs) | 
|  | 56 | + | 
|  | 57 | +	for _, xproto := range xprotopkgs { | 
|  | 58 | +		rxpath := strings.TrimPrefix(xproto.GoImportName, bpath) | 
|  | 59 | +		xpath := filepath.Join(sourcePath, rxpath) | 
|  | 60 | + | 
|  | 61 | +		xmsgs, err := DiscoverModule(xpath) | 
|  | 62 | +		if err != nil { | 
|  | 63 | +			return nil, err | 
|  | 64 | +		} | 
|  | 65 | + | 
|  | 66 | +		msgs[xproto.GoImportName] = xmsgs | 
|  | 67 | +	} | 
|  | 68 | + | 
|  | 69 | +	return msgs, nil | 
|  | 70 | +} | 
|  | 71 | + | 
|  | 72 | +// DiscoverModule discovers sdk messages defined in a module that resides under modulePath. | 
|  | 73 | +func DiscoverModule(modulePath string) (msgs []string, err error) { | 
|  | 74 | +	// parse go packages/files under modulePath. | 
|  | 75 | +	fset := token.NewFileSet() | 
|  | 76 | + | 
|  | 77 | +	pkgs, err := parser.ParseDir(fset, modulePath, nil, 0) | 
|  | 78 | +	if err != nil { | 
|  | 79 | +		return nil, err | 
|  | 80 | +	} | 
|  | 81 | + | 
|  | 82 | +	// collect all structs under modulePath to find out the ones that satisfy requirements. | 
|  | 83 | +	structs := make(map[string]requirements) | 
|  | 84 | + | 
|  | 85 | +	for _, pkg := range pkgs { | 
|  | 86 | +		for _, f := range pkg.Files { | 
|  | 87 | +			ast.Inspect(f, func(n ast.Node) bool { | 
|  | 88 | +				// look for struct methods. | 
|  | 89 | +				fdecl, ok := n.(*ast.FuncDecl) | 
|  | 90 | +				if !ok { | 
|  | 91 | +					return true | 
|  | 92 | +				} | 
|  | 93 | + | 
|  | 94 | +				// not a method. | 
|  | 95 | +				if fdecl.Recv == nil { | 
|  | 96 | +					return true | 
|  | 97 | +				} | 
|  | 98 | + | 
|  | 99 | +				// fname is the name of method. | 
|  | 100 | +				fname := fdecl.Name.Name | 
|  | 101 | + | 
|  | 102 | +				// find the struct name that method belongs to. | 
|  | 103 | +				sexp, ok := fdecl.Recv.List[0].Type.(*ast.StarExpr) | 
|  | 104 | +				if !ok { | 
|  | 105 | +					return true | 
|  | 106 | +				} | 
|  | 107 | + | 
|  | 108 | +				sname := sexp.X.(*ast.Ident).Name | 
|  | 109 | + | 
|  | 110 | +				// mark the requirement that this struct satisfies. | 
|  | 111 | +				if _, ok := structs[sname]; !ok { | 
|  | 112 | +					structs[sname] = newRequirements() | 
|  | 113 | +				} | 
|  | 114 | + | 
|  | 115 | +				structs[sname][fname] = true | 
|  | 116 | + | 
|  | 117 | +				return true | 
|  | 118 | +			}) | 
|  | 119 | +		} | 
|  | 120 | +	} | 
|  | 121 | + | 
|  | 122 | +	// checkRequirements checks if all requirements are satisfied. | 
|  | 123 | +	checkRequirements := func(r requirements) bool { | 
|  | 124 | +		for _, name := range r { | 
|  | 125 | +			if name == false { | 
|  | 126 | +				return false | 
|  | 127 | +			} | 
|  | 128 | +		} | 
|  | 129 | +		return true | 
|  | 130 | +	} | 
|  | 131 | + | 
|  | 132 | +	for name, reqs := range structs { | 
|  | 133 | +		if checkRequirements(reqs) { | 
|  | 134 | +			msgs = append(msgs, name) | 
|  | 135 | +		} | 
|  | 136 | +	} | 
|  | 137 | + | 
|  | 138 | +	return msgs, nil | 
|  | 139 | +} | 
|  | 140 | + | 
|  | 141 | +func findModuleProtoPkgs(sourcePath, bpath string) ([]protoanalysis.Package, error) { | 
|  | 142 | +	// find out all proto packages inside blockchain. | 
|  | 143 | +	allprotopkgs, err := protoanalysis.DiscoverPackages(sourcePath) | 
|  | 144 | +	if err != nil { | 
|  | 145 | +		return nil, err | 
|  | 146 | +	} | 
|  | 147 | + | 
|  | 148 | +	// filter out proto packages that does not reprents x/ modules of blockchain. | 
|  | 149 | +	var xprotopkgs []protoanalysis.Package | 
|  | 150 | +	for _, pkg := range allprotopkgs { | 
|  | 151 | +		if !strings.HasPrefix(pkg.GoImportName, bpath) { | 
|  | 152 | +			continue | 
|  | 153 | +		} | 
|  | 154 | + | 
|  | 155 | +		xprotopkgs = append(xprotopkgs, pkg) | 
|  | 156 | +	} | 
|  | 157 | + | 
|  | 158 | +	return xprotopkgs, nil | 
|  | 159 | +} | 
0 commit comments