docs(taiko): autogen json-rpc docs (#417)

* init script

* .

* .

* .

* .

* rename

* rename

* lint

* apply changes
This commit is contained in:
Roger 2025-04-15 08:49:07 -07:00 committed by GitHub
parent 2f9a84e4cb
commit 2fc319d9e7
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 369 additions and 0 deletions

42
.github/workflows/taiko-geth--pages.yml vendored Normal file
View 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

View file

@ -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`). 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 ## Go Ethereum
Golang execution layer implementation of the Ethereum protocol. Golang execution layer implementation of the Ethereum protocol.

View 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 := &eth.TaikoAPIBackend{}
auth := &eth.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.")
}