mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-07-27 23:26:44 +00:00
391 lines
13 KiB
Go
391 lines
13 KiB
Go
// Copyright 2015 The go-ethereum Authors
|
|
// This file is part of the go-ethereum library.
|
|
//
|
|
// The go-ethereum library is free software: you can redistribute it and/or modify
|
|
// it under the terms of the GNU Lesser General Public License as published by
|
|
// the Free Software Foundation, either version 3 of the License, or
|
|
// (at your option) any later version.
|
|
//
|
|
// The go-ethereum library is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU Lesser General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU Lesser General Public License
|
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
// This is the Go API auto-generator for the RPC APIs.
|
|
package main //build !none
|
|
|
|
//go:generate go run generator.go
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"go/ast"
|
|
"go/parser"
|
|
"go/token"
|
|
"io/ioutil"
|
|
"log"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"reflect"
|
|
"regexp"
|
|
"sort"
|
|
"strings"
|
|
"unicode"
|
|
|
|
"golang.org/x/tools/go/loader"
|
|
"golang.org/x/tools/go/types"
|
|
"golang.org/x/tools/imports"
|
|
)
|
|
|
|
// Package description from the go list command
|
|
type Package struct {
|
|
Name string
|
|
Dir string
|
|
GoFiles []string
|
|
}
|
|
|
|
type Endpoint struct {
|
|
Method string
|
|
Function string
|
|
Params []string
|
|
Return string
|
|
}
|
|
|
|
func main() {
|
|
// Load the entire package and dependencies for static analysis
|
|
conf := new(loader.Config)
|
|
conf.Import("github.com/ethereum/go-ethereum/rpc/api")
|
|
|
|
prog, err := conf.Load()
|
|
if err != nil {
|
|
log.Fatalf("Failed to load API package: %v", err)
|
|
}
|
|
info := prog.Imported["github.com/ethereum/go-ethereum/rpc/api"].Info
|
|
|
|
// Iterate over all the API files and collect the top level declarations
|
|
api, err := details("github.com/ethereum/go-ethereum/rpc/api")
|
|
if err != nil {
|
|
log.Fatalf("Failed to retrieve API package details: %v", err)
|
|
}
|
|
funs, types, values, err := declarations(api)
|
|
if err != nil {
|
|
log.Fatalf("Failed to collect API declarations: %v", err)
|
|
}
|
|
// Gather all the deteced API endpoints
|
|
methods, err := endpoints(info, funs, types, values)
|
|
if err != nil {
|
|
log.Fatalf("Failed to gather API endpoints: %v", err)
|
|
}
|
|
// Generate the client API and output if successfull
|
|
if code, err := generate(methods); err != nil {
|
|
log.Fatalf("Failed to format output code: %v", err)
|
|
} else if err := ioutil.WriteFile("../genapi.go", code, 0600); err != nil {
|
|
log.Fatalf("Failed to write output code: %v", err)
|
|
}
|
|
}
|
|
|
|
// details loads the metadata of the Go package.
|
|
func details(name string) (*Package, error) {
|
|
// Create the command to retrieve the package infos
|
|
cmd := exec.Command("go", "list", "-e", "-json", name)
|
|
|
|
// Retrieve the output, redirect the errors
|
|
out, err := cmd.StdoutPipe()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
cmd.Stderr = os.Stderr
|
|
|
|
// Start executing and parse the results
|
|
if err := cmd.Start(); err != nil {
|
|
return nil, err
|
|
}
|
|
defer cmd.Process.Kill()
|
|
|
|
info := new(Package)
|
|
if err := json.NewDecoder(out).Decode(&info); err != nil {
|
|
return nil, err
|
|
}
|
|
// Clean up and return
|
|
if err := cmd.Wait(); err != nil {
|
|
return nil, err
|
|
}
|
|
return info, nil
|
|
}
|
|
|
|
// declarations iterates over a package contents and collects type and value declarations.
|
|
func declarations(pack *Package) (map[string]*ast.BlockStmt, map[string]*ast.TypeSpec, map[string]ast.Expr, error) {
|
|
funs := make(map[string]*ast.BlockStmt)
|
|
types := make(map[string]*ast.TypeSpec)
|
|
values := make(map[string]ast.Expr)
|
|
|
|
for _, path := range pack.GoFiles {
|
|
// Parse the specified source file
|
|
fileSet := token.NewFileSet()
|
|
tree, err := parser.ParseFile(fileSet, filepath.Join(pack.Dir, path), nil, parser.ParseComments)
|
|
if err != nil {
|
|
return nil, nil, nil, err
|
|
}
|
|
// Collect all top level declarations
|
|
for _, decl := range tree.Decls {
|
|
switch decl := decl.(type) {
|
|
case *ast.FuncDecl:
|
|
if decl.Recv != nil {
|
|
recv := decl.Recv.List[0].Type.(*ast.StarExpr).X.(*ast.Ident).String()
|
|
call := decl.Name.String()
|
|
funs[recv+"."+call] = decl.Body
|
|
}
|
|
case *ast.GenDecl:
|
|
for _, spec := range decl.Specs {
|
|
switch spec := spec.(type) {
|
|
case *ast.ValueSpec:
|
|
for i, name := range spec.Names {
|
|
values[name.String()] = spec.Values[i]
|
|
}
|
|
case *ast.TypeSpec:
|
|
types[spec.Name.String()] = spec
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return funs, types, values, nil
|
|
}
|
|
|
|
// returns recursively iterates over a block statement and extracts all the
|
|
// return statements that contain nil errors.
|
|
func returns(block *ast.BlockStmt) []*ast.ReturnStmt {
|
|
results := []*ast.ReturnStmt{}
|
|
|
|
for _, stmt := range block.List {
|
|
switch stmt := stmt.(type) {
|
|
case *ast.ReturnStmt:
|
|
if ident, ok := stmt.Results[1].(*ast.Ident); ok {
|
|
if ident.String() == "nil" {
|
|
results = append(results, stmt)
|
|
}
|
|
}
|
|
case *ast.IfStmt:
|
|
results = append(results, returns(stmt.Body)...)
|
|
}
|
|
}
|
|
return results
|
|
}
|
|
|
|
// flatten converts a possibly multi selextor expression into a string identifier.
|
|
func flatten(sel ast.Expr) string {
|
|
switch x := sel.(type) {
|
|
case *ast.Ident:
|
|
return x.String()
|
|
case *ast.SelectorExpr:
|
|
return flatten(x.X) + "." + x.Sel.String()
|
|
case *ast.CallExpr:
|
|
return flatten(x.Fun) + "()"
|
|
default:
|
|
//fmt.Println("unknown selector to flatten", reflect.TypeOf(sel.X))
|
|
}
|
|
return ""
|
|
}
|
|
|
|
// endpoints collects the detected RPC API method endpoints.
|
|
func endpoints(info types.Info, funs map[string]*ast.BlockStmt, typeDecls map[string]*ast.TypeSpec, values map[string]ast.Expr) (map[string][]*Endpoint, error) {
|
|
methods := make(map[string][]*Endpoint)
|
|
|
|
// Iterate over all the API mappings, and locate the RPC function associations
|
|
for variable, val := range values {
|
|
if strings.HasSuffix(variable, "Mapping") {
|
|
// Iterate over all the exposed functionality and extract them
|
|
for _, mapping := range val.(*ast.CompositeLit).Elts {
|
|
// Fetch the name of the RPC function, and the associated argument definition
|
|
call := strings.Trim(mapping.(*ast.KeyValueExpr).Key.(*ast.BasicLit).Value, "\"")
|
|
impl := mapping.(*ast.KeyValueExpr).Value.(*ast.SelectorExpr)
|
|
args := mapping.(*ast.KeyValueExpr).Value.(*ast.SelectorExpr).Sel.Name + "Args"
|
|
|
|
// Generate the submodule and function names
|
|
module := strings.Split(call, "_")[0]
|
|
module = string(unicode.ToUpper(rune(module[0]))) + module[1:]
|
|
function := strings.Split(call, "_")[1]
|
|
function = string(unicode.ToUpper(rune(function[0]))) + function[1:]
|
|
|
|
// Generate the parameter list
|
|
params, paramList := []string{}, []string{}
|
|
if arg := typeDecls[args]; arg != nil {
|
|
for _, field := range arg.Type.(*ast.StructType).Fields.List {
|
|
variable := field.Names[0].String()
|
|
variable = string(unicode.ToLower(rune(variable[0]))) + variable[1:]
|
|
|
|
kind := ""
|
|
switch t := field.Type.(type) {
|
|
case *ast.Ident:
|
|
kind = t.String()
|
|
case *ast.SelectorExpr:
|
|
kind = t.X.(*ast.Ident).String() + "." + t.Sel.String()
|
|
case *ast.StarExpr:
|
|
switch sub := t.X.(type) {
|
|
case *ast.Ident:
|
|
kind = "*" + sub.String()
|
|
case *ast.SelectorExpr:
|
|
kind = "*" + sub.X.(*ast.Ident).String() + "." + sub.Sel.String()
|
|
default:
|
|
return nil, fmt.Errorf("Unknown pointer subtype: %v", sub)
|
|
}
|
|
default:
|
|
return nil, fmt.Errorf("Unknown type: %v", t)
|
|
}
|
|
params = append(params, fmt.Sprintf("%s %s", variable, kind))
|
|
paramList = append(paramList, variable)
|
|
}
|
|
}
|
|
// Try to detect and generate the return type
|
|
owner := impl.X.(*ast.ParenExpr).X.(*ast.StarExpr).X.(*ast.Ident).String()
|
|
method := mapping.(*ast.KeyValueExpr).Value.(*ast.SelectorExpr).Sel.String()
|
|
rets := returns(funs[owner+"."+method])
|
|
|
|
result := "interface{}"
|
|
for _, ret := range rets {
|
|
switch res := ret.Results[0].(type) {
|
|
case *ast.Ident:
|
|
if res.String() == "nil" {
|
|
break
|
|
} else if res.String() == "true" || res.String() == "false" {
|
|
result = "bool"
|
|
} else {
|
|
fmt.Println(owner, method, res, "unknown ident")
|
|
}
|
|
case *ast.CallExpr:
|
|
if ident, ok := res.Fun.(*ast.Ident); ok {
|
|
if ident.String() == "string" {
|
|
result = "string"
|
|
} else if ident.String() == "newHexNum" {
|
|
result = "int64"
|
|
} else if ident.String() == "newHexData" {
|
|
result = "[]byte"
|
|
} else {
|
|
for match, def := range info.Defs {
|
|
if ident.String() == match.String() {
|
|
result = def.Type().(*types.Signature).Results().At(0).Type().String()
|
|
if strings.Contains(result, "/") {
|
|
result = regexp.MustCompile("[a-zA-Z0-9-\\.]+/").ReplaceAllString(result, "")
|
|
}
|
|
}
|
|
}
|
|
if result == "interface{}" {
|
|
fmt.Println(owner, method, res, "unknown ident funcion")
|
|
}
|
|
}
|
|
} else if sel, ok := res.Fun.(*ast.SelectorExpr); ok {
|
|
if selector := flatten(sel); selector != "" {
|
|
for match, selection := range info.Selections {
|
|
if flatten(match) == selector {
|
|
result = selection.Type().(*types.Signature).Results().At(0).Type().String()
|
|
if strings.Contains(result, "/") {
|
|
result = regexp.MustCompile("[a-zA-Z0-9-\\.]+/").ReplaceAllString(result, "")
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
fmt.Println(owner, method, res, "call", res.Fun, reflect.TypeOf(res.Fun))
|
|
}
|
|
default:
|
|
fmt.Println(owner, method, res, reflect.TypeOf(ret.Results[0]))
|
|
}
|
|
}
|
|
// Insert the function to the submodule collection (alphabetically)
|
|
methods[module] = append(methods[module], &Endpoint{
|
|
Method: call,
|
|
Function: fmt.Sprintf("%s(%s)", function, strings.Join(params, ",")),
|
|
Params: paramList,
|
|
Return: result,
|
|
})
|
|
}
|
|
}
|
|
}
|
|
return methods, nil
|
|
}
|
|
|
|
// generate creates the client code belonging to a set of API method endpoints.
|
|
func generate(methods map[string][]*Endpoint) ([]byte, error) {
|
|
// Create a sorted list of API modules and endpoints to generate
|
|
modules := make([]string, 0, len(methods))
|
|
for module, _ := range methods {
|
|
modules = append(modules, module)
|
|
}
|
|
sort.Strings(modules)
|
|
|
|
for _, module := range modules {
|
|
for i := 0; i < len(methods[module]); i++ {
|
|
for j := i + 1; j < len(methods[module]); j++ {
|
|
if methods[module][i].Function > methods[module][j].Function {
|
|
methods[module][i], methods[module][j] = methods[module][j], methods[module][i]
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// Start generating the client API
|
|
client := "package rpc\n"
|
|
|
|
// Generate the client struct with all its submodules
|
|
client += fmt.Sprintf("type GenApi struct {\n")
|
|
for _, module := range modules {
|
|
client += fmt.Sprintf("%s *%s\n", module, module)
|
|
}
|
|
client += fmt.Sprintf("}\n")
|
|
|
|
// Generate the API constructor to create the individual submodules
|
|
client += fmt.Sprint("func NewGenApi(client comms.EthereumClient) *GenApi {\n")
|
|
client += fmt.Sprint("xeth := NewXeth(client)\n\n")
|
|
client += fmt.Sprint("return &GenApi{\n")
|
|
for _, module := range modules {
|
|
client += fmt.Sprintf("%s: &%s{xeth},\n", module, module)
|
|
}
|
|
client += fmt.Sprintf("}}\n")
|
|
|
|
// Generate each of the client API calls
|
|
for _, module := range modules {
|
|
endpoints := methods[module]
|
|
|
|
client += fmt.Sprintf("\ntype %s struct {\n xeth *Xeth\n}\n", module)
|
|
for _, endpoint := range endpoints {
|
|
// Generate the header (only add variables if conversions are required)
|
|
if endpoint.Return == "interface{}" {
|
|
client += fmt.Sprintf("func (self *%s) %s (interface{}, error) {\n", module, endpoint.Function)
|
|
} else {
|
|
client += fmt.Sprintf("func (self *%s) %s (result %s, failure error) {\n", module, endpoint.Function, endpoint.Return)
|
|
}
|
|
// Generate the actual function invocation (straight return if no return type is known)
|
|
invocation := fmt.Sprintf("self.xeth.Call(\"%s\", nil)", endpoint.Method)
|
|
if len(endpoint.Params) > 0 {
|
|
invocation = fmt.Sprintf("self.xeth.Call(\"%s\", []interface{}{%s})", endpoint.Method, strings.Join(endpoint.Params, ","))
|
|
}
|
|
if endpoint.Return == "interface{}" {
|
|
client += fmt.Sprintf("return %s\n", invocation)
|
|
} else {
|
|
client += fmt.Sprintf("res, err := %s\n", invocation)
|
|
}
|
|
// If conversions are needed, check for errors and post process
|
|
if endpoint.Return != "interface{}" {
|
|
client += fmt.Sprintf("if err != nil { failure = err; return; }\n")
|
|
|
|
if endpoint.Return == "int64" {
|
|
client += fmt.Sprintf("return new(big.Int).SetBytes(common.FromHex(res.(string))).Int64(), nil\n")
|
|
} else if endpoint.Return == "*big.Int" {
|
|
client += fmt.Sprintf("return new(big.Int).SetBytes(common.FromHex(res.(string))), nil\n")
|
|
} else if endpoint.Return == "[]byte" {
|
|
client += fmt.Sprintf("return res.([]byte), nil\n")
|
|
} else if strings.HasPrefix(endpoint.Return, "[]") {
|
|
client += fmt.Sprintf("for _, item := range res.([]interface{}) { result = append(result, item.(%s))}; return\n", endpoint.Return[2:])
|
|
} else {
|
|
client += fmt.Sprintf("return res.(%s), nil\n", endpoint.Return)
|
|
}
|
|
}
|
|
client += fmt.Sprintf("}\n")
|
|
}
|
|
}
|
|
// Format the final code and return
|
|
return imports.Process("", []byte(client), nil)
|
|
}
|