mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-07-26 06:36:43 +00:00
docs(taiko): autogen json-rpc docs (#417)
* init script * . * . * . * . * rename * rename * lint * apply changes
This commit is contained in:
parent
2f9a84e4cb
commit
2fc319d9e7
3 changed files with 369 additions and 0 deletions
42
.github/workflows/taiko-geth--pages.yml
vendored
Normal file
42
.github/workflows/taiko-geth--pages.yml
vendored
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
name: Taiko Geth JSON-RPC Autogen Docs
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [taiko]
|
||||
|
||||
jobs:
|
||||
generate-and-deploy:
|
||||
runs-on: [arc-runner-set]
|
||||
permissions:
|
||||
contents: write
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: "1.23"
|
||||
|
||||
- name: Set up node
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: "20"
|
||||
|
||||
- name: Install jrgen
|
||||
run: |
|
||||
npm install -g jrgen
|
||||
|
||||
- name: Run script
|
||||
run: |
|
||||
go run ./scripts/generate-jsonrpc.go
|
||||
jrgen docs-html jrgen.json
|
||||
mv taiko-json-rpc-api-reference.html index.html
|
||||
|
||||
- name: Deploy to GitHub Pages
|
||||
uses: peaceiris/actions-gh-pages@v4
|
||||
with:
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
publish_dir: ./ # Set this to where your `index.html` is located
|
||||
publish_branch: gh-pages
|
||||
destination_dir: taiko-geth-json-rpc
|
||||
|
|
@ -8,6 +8,10 @@ The codebase is based on [go-ethereum v1.15.5](https://github.com/ethereum/go-et
|
|||
|
||||
All source code changes by [taiko](https://taiko.xyz) team are commented with `"CHANGE(taiko): ...."`, and the filenames of all additional files have the prefix `"taiko_"` (`taiko_*.go`).
|
||||
|
||||
## JSON-RPC API
|
||||
|
||||
JSON-RPC documentation for taiko-specific JSON-RPC calls can be found [here](https://taikoxyz.github.io/taiko-geth/taiko-geth-json-rpc/).
|
||||
|
||||
## Go Ethereum
|
||||
|
||||
Golang execution layer implementation of the Ethereum protocol.
|
||||
|
|
|
|||
323
scripts/taiko_generate_jsonrpc.go
Normal file
323
scripts/taiko_generate_jsonrpc.go
Normal file
|
|
@ -0,0 +1,323 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/parser"
|
||||
"go/token"
|
||||
"log"
|
||||
"os"
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
"github.com/ethereum/go-ethereum/eth"
|
||||
)
|
||||
|
||||
type JRGenSpec struct {
|
||||
Schema string `json:"$schema"`
|
||||
JRGen string `json:"jrgen"`
|
||||
JSONRPC string `json:"jsonrpc"`
|
||||
Info Info `json:"info"`
|
||||
Definitions map[string]interface{} `json:"definitions"`
|
||||
Methods map[string]Method `json:"methods"`
|
||||
}
|
||||
|
||||
type Info struct {
|
||||
Title string `json:"title"`
|
||||
Description []string `json:"description"`
|
||||
Version string `json:"version"`
|
||||
Servers []Server `json:"servers,omitempty"`
|
||||
}
|
||||
|
||||
type Server struct {
|
||||
URL string `json:"url"`
|
||||
Description string `json:"description"`
|
||||
}
|
||||
|
||||
type Method struct {
|
||||
Summary string `json:"summary"`
|
||||
Description string `json:"description"`
|
||||
Tags []string `json:"tags,omitempty"`
|
||||
Params map[string]interface{} `json:"params,omitempty"`
|
||||
Result map[string]interface{} `json:"result,omitempty"`
|
||||
}
|
||||
|
||||
func parseParamNamesFromFile(filename string) (map[string][]string, map[string]string, error) {
|
||||
fset := token.NewFileSet()
|
||||
fileAST, err := parser.ParseFile(fset, filename, nil, parser.ParseComments)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
methodParams := make(map[string][]string)
|
||||
methodDocs := make(map[string]string)
|
||||
|
||||
for _, decl := range fileAST.Decls {
|
||||
if funcDecl, ok := decl.(*ast.FuncDecl); ok && funcDecl.Recv != nil {
|
||||
recvField := funcDecl.Recv.List[0]
|
||||
var recvTypeStr string
|
||||
|
||||
switch t := recvField.Type.(type) {
|
||||
case *ast.StarExpr:
|
||||
if ident, ok := t.X.(*ast.Ident); ok {
|
||||
recvTypeStr = "*" + ident.Name
|
||||
}
|
||||
case *ast.Ident:
|
||||
recvTypeStr = t.Name
|
||||
}
|
||||
|
||||
if recvTypeStr == "*TaikoAPIBackend" || recvTypeStr == "*TaikoAuthAPIBackend" {
|
||||
var params []string
|
||||
if funcDecl.Type.Params != nil {
|
||||
for _, field := range funcDecl.Type.Params.List {
|
||||
if len(field.Names) > 0 {
|
||||
for _, name := range field.Names {
|
||||
params = append(params, name.Name)
|
||||
}
|
||||
} else {
|
||||
params = append(params, exprToString(field.Type))
|
||||
}
|
||||
}
|
||||
}
|
||||
key := recvTypeStr + "." + funcDecl.Name.Name
|
||||
methodParams[key] = params
|
||||
|
||||
if funcDecl.Doc != nil {
|
||||
methodDocs[key] = strings.TrimSpace(funcDecl.Doc.Text())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return methodParams, methodDocs, nil
|
||||
}
|
||||
|
||||
func exprToString(expr ast.Expr) string {
|
||||
switch t := expr.(type) {
|
||||
case *ast.Ident:
|
||||
return t.Name
|
||||
case *ast.StarExpr:
|
||||
return "*" + exprToString(t.X)
|
||||
case *ast.ArrayType:
|
||||
return "[]" + exprToString(t.Elt)
|
||||
case *ast.SelectorExpr:
|
||||
return exprToString(t.X) + "." + t.Sel.Name
|
||||
default:
|
||||
return fmt.Sprintf("%T", expr)
|
||||
}
|
||||
}
|
||||
|
||||
func generateReturnSchema(goType reflect.Type) map[string]interface{} {
|
||||
switch goType.Kind() {
|
||||
case reflect.Ptr:
|
||||
return map[string]interface{}{
|
||||
"$ref": "#/definitions/" + goType.String(),
|
||||
}
|
||||
case reflect.Slice:
|
||||
elem := goType.Elem()
|
||||
if elem.Kind() == reflect.Ptr {
|
||||
return map[string]interface{}{
|
||||
"type": "array",
|
||||
"items": map[string]interface{}{
|
||||
"$ref": "#/definitions/" + elem.String(),
|
||||
},
|
||||
}
|
||||
}
|
||||
return map[string]interface{}{
|
||||
"type": "array",
|
||||
"items": generateTypeRef(elem),
|
||||
}
|
||||
default:
|
||||
return generateTypeRef(goType)
|
||||
}
|
||||
}
|
||||
|
||||
func generateTypeRef(goType reflect.Type) map[string]interface{} {
|
||||
switch goType.Kind() {
|
||||
case reflect.Ptr:
|
||||
switch goType.String() {
|
||||
case "*miner.PreBuiltTxList":
|
||||
return map[string]interface{}{
|
||||
"$ref": "#/definitions/*miner.PreBuiltTxList",
|
||||
}
|
||||
case "*big.Int":
|
||||
return map[string]interface{}{
|
||||
"$ref": "#/definitions/*big.Int",
|
||||
}
|
||||
case "*rawdb.L1Origin":
|
||||
return map[string]interface{}{
|
||||
"$ref": "#/definitions/*rawdb.L1Origin",
|
||||
}
|
||||
case "*math.HexOrDecimal256":
|
||||
return map[string]interface{}{
|
||||
"$ref": "#/definitions/*math.HexOrDecimal256",
|
||||
}
|
||||
}
|
||||
case reflect.Slice:
|
||||
elem := goType.Elem()
|
||||
if elem.Kind() == reflect.Ptr {
|
||||
return map[string]interface{}{
|
||||
"type": "array",
|
||||
"items": map[string]interface{}{
|
||||
"$ref": "#/definitions/" + elem.String(),
|
||||
},
|
||||
}
|
||||
}
|
||||
return map[string]interface{}{
|
||||
"type": "array",
|
||||
"items": generateTypeRef(elem),
|
||||
}
|
||||
case reflect.String:
|
||||
return map[string]interface{}{
|
||||
"type": "string",
|
||||
"examples": []string{"0x123456"},
|
||||
}
|
||||
case reflect.Int, reflect.Int64, reflect.Uint, reflect.Uint64:
|
||||
return map[string]interface{}{
|
||||
"type": "integer",
|
||||
"examples": []int{10000},
|
||||
}
|
||||
case reflect.Bool:
|
||||
return map[string]interface{}{
|
||||
"type": "boolean",
|
||||
"examples": []bool{true},
|
||||
}
|
||||
}
|
||||
|
||||
return map[string]interface{}{
|
||||
"type": "string",
|
||||
"examples": []string{"0x123456"},
|
||||
}
|
||||
}
|
||||
|
||||
func extractMethods(obj interface{}, prefix string, paramMapping map[string][]string, docMapping map[string]string) map[string]Method {
|
||||
t := reflect.TypeOf(obj)
|
||||
methods := make(map[string]Method)
|
||||
|
||||
var recName string
|
||||
if t.Kind() == reflect.Ptr {
|
||||
recName = "*" + t.Elem().Name()
|
||||
} else {
|
||||
recName = t.Name()
|
||||
}
|
||||
|
||||
for i := 0; i < t.NumMethod(); i++ {
|
||||
m := t.Method(i)
|
||||
key := recName + "." + m.Name
|
||||
mappedNames := paramMapping[key]
|
||||
|
||||
props := map[string]interface{}{}
|
||||
required := []string{}
|
||||
|
||||
for j := 1; j < m.Type.NumIn(); j++ {
|
||||
paramType := m.Type.In(j)
|
||||
paramName := fmt.Sprintf("param%d", j-1)
|
||||
if j-1 < len(mappedNames) {
|
||||
paramName = mappedNames[j-1]
|
||||
}
|
||||
props[paramName] = generateTypeRef(paramType)
|
||||
required = append(required, paramName)
|
||||
}
|
||||
|
||||
var paramsObj map[string]interface{}
|
||||
if len(props) > 0 {
|
||||
paramsObj = map[string]interface{}{
|
||||
"type": "object",
|
||||
"properties": props,
|
||||
"required": required,
|
||||
}
|
||||
}
|
||||
|
||||
var resultType map[string]interface{}
|
||||
if m.Type.NumOut() > 0 {
|
||||
out := m.Type.Out(0)
|
||||
resultType = generateReturnSchema(out)
|
||||
}
|
||||
|
||||
description := fmt.Sprintf("Invokes the %s method on %s", m.Name, recName)
|
||||
if doc, ok := docMapping[key]; ok {
|
||||
description = doc
|
||||
}
|
||||
|
||||
method := Method{
|
||||
Summary: fmt.Sprintf("RPC method %s", m.Name),
|
||||
Description: description,
|
||||
Tags: []string{prefix},
|
||||
Params: paramsObj,
|
||||
Result: resultType,
|
||||
}
|
||||
|
||||
methods[prefix+m.Name] = method
|
||||
}
|
||||
return methods
|
||||
}
|
||||
|
||||
func main() {
|
||||
paramMapping, docMapping, err := parseParamNamesFromFile("eth/taiko_api_backend.go")
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to parse params: %v", err)
|
||||
}
|
||||
|
||||
spec := JRGenSpec{
|
||||
Schema: "https://rawgit.com/mzernetsch/jrgen/master/jrgen-spec.schema.json",
|
||||
JRGen: "1.2",
|
||||
JSONRPC: "2.0",
|
||||
Info: Info{
|
||||
Title: "Taiko JSON-RPC API",
|
||||
Version: "1.0",
|
||||
Description: []string{"Auto-generated JSON-RPC API for Taiko backend."},
|
||||
},
|
||||
Definitions: map[string]interface{}{
|
||||
"*miner.PreBuiltTxList": map[string]interface{}{
|
||||
"type": "object",
|
||||
"properties": map[string]interface{}{
|
||||
"TxList": map[string]interface{}{"type": "array"},
|
||||
"EstimatedGasUsed": map[string]interface{}{"type": "integer", "examples": []int{10000}},
|
||||
"BytesLength": map[string]interface{}{"type": "integer", "examples": []int{10000}},
|
||||
},
|
||||
"required": []string{"TxList", "EstimatedGasUsed", "BytesLength"},
|
||||
},
|
||||
"*big.Int": map[string]interface{}{
|
||||
"type": "integer",
|
||||
"examples": []int{10000},
|
||||
},
|
||||
"*rawdb.L1Origin": map[string]interface{}{
|
||||
"type": "object",
|
||||
"properties": map[string]interface{}{
|
||||
"blockID": map[string]interface{}{"$ref": "#/definitions/*big.Int"},
|
||||
"l2BlockHash": map[string]interface{}{"type": "string", "examples": []string{"0x123456"}},
|
||||
"l1BlockHeight": map[string]interface{}{"$ref": "#/definitions/*big.Int"},
|
||||
"l1BlockHash": map[string]interface{}{"type": "string", "examples": []string{"0x123456"}},
|
||||
},
|
||||
"required": []string{"blockID", "l2BlockHash", "l1BlockHeight", "l1BlockHash"},
|
||||
},
|
||||
"*math.HexOrDecimal256": map[string]interface{}{
|
||||
"$ref": "#/definitions/*big.Int",
|
||||
"description": "Hexadecimal or decimal representation of a number.",
|
||||
},
|
||||
},
|
||||
Methods: map[string]Method{},
|
||||
}
|
||||
|
||||
taiko := ð.TaikoAPIBackend{}
|
||||
auth := ð.TaikoAuthAPIBackend{}
|
||||
|
||||
for k, v := range extractMethods(taiko, "taiko_", paramMapping, docMapping) {
|
||||
spec.Methods[k] = v
|
||||
}
|
||||
for k, v := range extractMethods(auth, "taikoAuth_", paramMapping, docMapping) {
|
||||
spec.Methods[k] = v
|
||||
}
|
||||
|
||||
jsonData, err := json.MarshalIndent(spec, "", " ")
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to marshal JSON: %v", err)
|
||||
}
|
||||
err = os.WriteFile("jrgen.json", jsonData, 0644)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to write file: %v", err)
|
||||
}
|
||||
|
||||
fmt.Println("jrgen.json generated successfully.")
|
||||
}
|
||||
Loading…
Reference in a new issue