Unverified Commit bf693228 authored by Sina Mahmoodi's avatar Sina Mahmoodi Committed by GitHub

eth/tracers/js: goja tracer (#23773)

This adds a JS tracer runtime environment based on the Goja VM. The new
runtime replaces the duktape runtime, which will be removed soon.

Goja is implemented in Go and is faster for cases where the Go <-> JS
transition overhead dominates overall performance. It is faster because
duktape is written in C, and the transition cost includes the cost of using
cgo. Another reason for using Goja is that go-duktape is not maintained
anymore.

We expect the performace of JS tracing to be at least as good or better with
this change.
parent cc9fb8e2
......@@ -285,7 +285,7 @@ func TestPrettyError(t *testing.T) {
defer tester.Close(t)
tester.console.Evaluate("throw 'hello'")
want := jsre.ErrorColor("hello") + "\n\tat <eval>:1:7(1)\n\n"
want := jsre.ErrorColor("hello") + "\n\tat <eval>:1:1(1)\n\n"
if output := tester.output.String(); output != want {
t.Fatalf("pretty error mismatch: have %s, want %s", output, want)
}
......
......@@ -752,7 +752,7 @@ func TestRuntimeJSTracer(t *testing.T) {
byte(vm.CREATE),
byte(vm.POP),
},
results: []string{`"1,1,4294935775,6,12"`, `"1,1,4294935775,6,0"`},
results: []string{`"1,1,952855,6,12"`, `"1,1,952855,6,0"`},
},
{
// CREATE2
......@@ -768,7 +768,7 @@ func TestRuntimeJSTracer(t *testing.T) {
byte(vm.CREATE2),
byte(vm.POP),
},
results: []string{`"1,1,4294935766,6,13"`, `"1,1,4294935766,6,0"`},
results: []string{`"1,1,952846,6,13"`, `"1,1,952846,6,0"`},
},
{
// CALL
......@@ -781,7 +781,7 @@ func TestRuntimeJSTracer(t *testing.T) {
byte(vm.CALL),
byte(vm.POP),
},
results: []string{`"1,1,4294964716,6,13"`, `"1,1,4294964716,6,0"`},
results: []string{`"1,1,981796,6,13"`, `"1,1,981796,6,0"`},
},
{
// CALLCODE
......@@ -794,7 +794,7 @@ func TestRuntimeJSTracer(t *testing.T) {
byte(vm.CALLCODE),
byte(vm.POP),
},
results: []string{`"1,1,4294964716,6,13"`, `"1,1,4294964716,6,0"`},
results: []string{`"1,1,981796,6,13"`, `"1,1,981796,6,0"`},
},
{
// STATICCALL
......@@ -806,7 +806,7 @@ func TestRuntimeJSTracer(t *testing.T) {
byte(vm.STATICCALL),
byte(vm.POP),
},
results: []string{`"1,1,4294964719,6,12"`, `"1,1,4294964719,6,0"`},
results: []string{`"1,1,981799,6,12"`, `"1,1,981799,6,0"`},
},
{
// DELEGATECALL
......@@ -818,7 +818,7 @@ func TestRuntimeJSTracer(t *testing.T) {
byte(vm.DELEGATECALL),
byte(vm.POP),
},
results: []string{`"1,1,4294964719,6,12"`, `"1,1,4294964719,6,0"`},
results: []string{`"1,1,981799,6,12"`, `"1,1,981799,6,0"`},
},
{
// CALL self-destructing contract
......@@ -859,7 +859,8 @@ func TestRuntimeJSTracer(t *testing.T) {
t.Fatal(err)
}
_, _, err = Call(main, nil, &Config{
State: statedb,
GasLimit: 1000000,
State: statedb,
EVMConfig: vm.Config{
Debug: true,
Tracer: tracer,
......
......@@ -134,6 +134,10 @@ func TestCallTracerNative(t *testing.T) {
testCallTracer("callTracer", "call_tracer", t)
}
func TestCallTracerLegacyDuktape(t *testing.T) {
testCallTracer("callTracerLegacyDuktape", "call_tracer_legacy", t)
}
func testCallTracer(tracerName string, dirPath string, t *testing.T) {
files, err := os.ReadDir(filepath.Join("testdata", dirPath))
if err != nil {
......@@ -258,7 +262,7 @@ func BenchmarkTracers(b *testing.B) {
if err := json.Unmarshal(blob, test); err != nil {
b.Fatalf("failed to parse testcase: %v", err)
}
benchTracer("callTracerNative", test, b)
benchTracer("callTracer", test, b)
})
}
}
......
This diff is collapsed.
......@@ -17,7 +17,43 @@
// Package tracers contains the actual JavaScript tracer assets.
package tracers
import "embed"
import (
"embed"
"io/fs"
"strings"
"unicode"
)
//go:embed *.js
var FS embed.FS
var files embed.FS
// Load reads the built-in JS tracer files embedded in the binary and
// returns a mapping of tracer name to source.
func Load() (map[string]string, error) {
var assetTracers = make(map[string]string)
err := fs.WalkDir(files, ".", func(path string, d fs.DirEntry, err error) error {
if err != nil {
return err
}
if d.IsDir() {
return nil
}
b, err := fs.ReadFile(files, path)
if err != nil {
return err
}
name := camel(strings.TrimSuffix(path, ".js"))
assetTracers[name] = string(b)
return nil
})
return assetTracers, err
}
// camel converts a snake cased input string into a camel cased output.
func camel(str string) string {
pieces := strings.Split(str, "_")
for i := 1; i < len(pieces); i++ {
pieces[i] = string(unicode.ToUpper(rune(pieces[i][0]))) + pieces[i][1:]
}
return strings.Join(pieces, "")
}
......@@ -21,56 +21,40 @@ import (
"encoding/json"
"errors"
"fmt"
"io/fs"
"math/big"
"strings"
"sync/atomic"
"time"
"unicode"
"unsafe"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/crypto"
tracers2 "github.com/ethereum/go-ethereum/eth/tracers"
"github.com/ethereum/go-ethereum/eth/tracers/js/internal/tracers"
"github.com/ethereum/go-ethereum/eth/tracers"
jsassets "github.com/ethereum/go-ethereum/eth/tracers/js/internal/tracers"
"github.com/ethereum/go-ethereum/log"
"gopkg.in/olebedev/go-duktape.v3"
)
// camel converts a snake cased input string into a camel cased output.
func camel(str string) string {
pieces := strings.Split(str, "_")
for i := 1; i < len(pieces); i++ {
pieces[i] = string(unicode.ToUpper(rune(pieces[i][0]))) + pieces[i][1:]
}
return strings.Join(pieces, "")
}
var assetTracers = make(map[string]string)
// init retrieves the JavaScript transaction tracers included in go-ethereum.
func init() {
err := fs.WalkDir(tracers.FS, ".", func(path string, d fs.DirEntry, err error) error {
if err != nil {
return err
}
if d.IsDir() {
return nil
}
b, err := fs.ReadFile(tracers.FS, path)
if err != nil {
return err
}
name := camel(strings.TrimSuffix(path, ".js"))
assetTracers[name] = string(b)
return nil
})
assetTracers, err := jsassets.Load()
if err != nil {
panic(err)
}
tracers2.RegisterLookup(true, newJsTracer)
// TODO: Either disable duktape or solve conflicts between goja and duktape
tracers.RegisterLookup(false, func(name string, ctx *tracers.Context) (tracers.Tracer, error) {
if !strings.HasSuffix(name, "Duktape") {
return nil, errors.New("only suffix Duktape supported")
}
name = strings.TrimSuffix(name, "Duktape")
code, ok := assetTracers[name]
if !ok {
return nil, errors.New("only pre-built tracers supported")
}
return newJsTracer(code, ctx)
})
}
// makeSlice convert an unsafe memory pointer with the given type into a Go byte
......@@ -439,12 +423,9 @@ type jsTracer struct {
// New instantiates a new tracer instance. code specifies a Javascript snippet,
// which must evaluate to an expression returning an object with 'step', 'fault'
// and 'result' functions.
func newJsTracer(code string, ctx *tracers2.Context) (tracers2.Tracer, error) {
if c, ok := assetTracers[code]; ok {
code = c
}
func newJsTracer(code string, ctx *tracers.Context) (tracers.Tracer, error) {
if ctx == nil {
ctx = new(tracers2.Context)
ctx = new(tracers.Context)
}
tracer := &jsTracer{
vm: duktape.New(),
......
This diff is collapsed.
......@@ -19,7 +19,7 @@ require (
github.com/deckarep/golang-set v1.8.0
github.com/deepmap/oapi-codegen v1.8.2 // indirect
github.com/docker/docker v1.4.2-0.20180625184442-8e610b2b55bf
github.com/dop251/goja v0.0.0-20211011172007-d99e4b8cbf48
github.com/dop251/goja v0.0.0-20220405120441-9037c2b61cbf
github.com/edsrzf/mmap-go v1.0.0
github.com/fatih/color v1.7.0
github.com/fjl/gencodec v0.0.0-20220412091415-8bb9e558978c
......
......@@ -111,6 +111,8 @@ github.com/docker/docker v1.4.2-0.20180625184442-8e610b2b55bf h1:sh8rkQZavChcmak
github.com/docker/docker v1.4.2-0.20180625184442-8e610b2b55bf/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/dop251/goja v0.0.0-20211011172007-d99e4b8cbf48 h1:iZOop7pqsg+56twTopWgwCGxdB5SI2yDO8Ti7eTRliQ=
github.com/dop251/goja v0.0.0-20211011172007-d99e4b8cbf48/go.mod h1:R9ET47fwRVRPZnOGvHxxhuZcbrMCuiqOz3Rlrh4KSnk=
github.com/dop251/goja v0.0.0-20220405120441-9037c2b61cbf h1:Yt+4K30SdjOkRoRRm3vYNQgR+/ZIy0RmeUDZo7Y8zeQ=
github.com/dop251/goja v0.0.0-20220405120441-9037c2b61cbf/go.mod h1:R9ET47fwRVRPZnOGvHxxhuZcbrMCuiqOz3Rlrh4KSnk=
github.com/dop251/goja_nodejs v0.0.0-20210225215109-d91c329300e7/go.mod h1:hn7BA7c8pLvoGndExHudxTDKZ84Pyvv+90pbBjbTz0Y=
github.com/eclipse/paho.mqtt.golang v1.2.0/go.mod h1:H9keYFcgq3Qr5OUJm/JZI/i6U7joQ8SYLhZwfeOo6Ts=
github.com/edsrzf/mmap-go v1.0.0 h1:CEBF7HpRnUCSJgGUb5h1Gm7e3VkmVDrR8lvWVLtrOFw=
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment