Skip to content
Open
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion scripts/lint_allowed_geth_imports.sh
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ set -o pipefail
# 1. Recursively search through all go files for any lines that include a direct import from go-ethereum
# 2. Sort the unique results
# #. Print out the difference between the search results and the list of specified allowed package imports from geth.
extra_imports=$(grep -r --include='*.go' --exclude-dir='simulator' '"github.com/ethereum/go-ethereum/.*"' -o -h | sort -u | comm -23 - ./scripts/geth-allowed-packages.txt)
extra_imports=$(grep -Pr --include='*.go' --exclude-dir='simulator' '^\s+"github.com/ethereum/go-ethereum/.*"' -o -h | tr -d '[:blank:]' | sort -u | comm -23 - ./scripts/geth-allowed-packages.txt)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There was a false positive on const geth = "github.com/ethereum/go-ethereum/" so I updated the check to only match with (possible) whitespace but no other prefix.

if [ -n "${extra_imports}" ]; then
echo "new go-ethereum imports should be added to ./scripts/geth-allowed-packages.txt to prevent accidental imports:"
echo "${extra_imports}"
Expand Down
3 changes: 3 additions & 0 deletions x/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Experimental

Code in this directory is experimental and MUST NOT be relied on to be stable.
5 changes: 5 additions & 0 deletions x/gethclone/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# `gethclone`

This is an experimental module for tracking upstream `go-ethereum` changes.
The approach of `git merge`ing the upstream branch into `subnet-evm` is brittle as it relies on purely syntactic patching.
`gethclone` is intended to follow a rebase-like pattern, applying a set of semantic patches (e.g. AST modification, [Uber's `gopatch`](https://pkg.go.dev/github.com/uber-go/gopatch), etc.) that (a) should be more robust to refactoring; and (b) act as explicit documentation of the diffs.
91 changes: 91 additions & 0 deletions x/gethclone/astpatch/astpatch.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
// Package astpatch provides functionality for traversing and modifying Go
// syntax trees. It extends the astutil package with reusable "patches".
package astpatch

import (
"go/ast"
"reflect"

"golang.org/x/tools/go/ast/astutil"
)

type (
// A Patch (optionally) modifies an AST node; it is equivalent to an
// `astutil.ApplyFunc` except that it returns an error instead of a boolean.
// A non-nil error is equivalent to returning false and will also abort all
// further calls to other patches.
Patch func(*astutil.Cursor) error
// A PatchRegistry maps [Go package path] -> [ast.Node concrete types] ->
// [all `Patch` functions that must be applied to said node types in said
// package].
//
// The special `pkgPath` value "*" will match all package paths.
PatchRegistry map[string]map[reflect.Type][]Patch
)

// Add is a convenience wrapper for registering a new `Patch` in the registry.
// The `zeroNode` can be any type (including nil pointers) that implements
// `ast.Node`.
//
// The special `pkgPath` value "*" will match all package paths. While there is
// no specific requirement for `pkgPath` other than it matching the equivalent
// argument passed to `Apply()`, it is typically sourced from
// `golang.org/x/tools/go/packages.Package.PkgPath`.
func (r PatchRegistry) Add(pkgPath string, zeroNode ast.Node, fn Patch) {
pkg, ok := r[pkgPath]
if !ok {
pkg = make(map[reflect.Type][]Patch)
r[pkgPath] = pkg
}

t := nodeType(zeroNode)
pkg[t] = append(pkg[t], fn)
}

// Apply calls `astutil.Apply()` on `node`, calling the appropriate `Patch`
// functions as the syntax tree is traversed. Patches are applied as the `pre`
// argument to `astutil.Apply()`.
//
// Global `pkgPath` matches (i.e. to those registered with "*") will be applied
// before package-specific matches.
//
// If any `Patch` returns an error then no further patches will be called, and
// the error will be returned by `Apply()`.
func (r PatchRegistry) Apply(pkgPath string, node ast.Node) error {
var err error
astutil.Apply(node, func(c *astutil.Cursor) bool {
if err != nil {
return false
}
if err = r.applyToCursor("*", c); err != nil {
return false
}
err = r.applyToCursor(pkgPath, c)
return err == nil
}, nil)
return err
}

func (r PatchRegistry) applyToCursor(pkgPath string, c *astutil.Cursor) error {
if c.Node() == nil {
return nil
}

pkg, ok := r[pkgPath]
if !ok {
return nil
}
for _, fn := range pkg[nodeType(c.Node())] {
if err := fn(c); err != nil {
return err
}
}
return nil
}

// nodeType returns the `reflect.Type` of the _concrete_ type implementing
// `ast.Node`. Simpy calling `reflect.TypeOf(n)` would be incorrect as it would
// reflect the interface (and not match any nodes).
func nodeType(n ast.Node) reflect.Type {
return reflect.ValueOf(n).Type()
}
121 changes: 121 additions & 0 deletions x/gethclone/astpatch/astpatch_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
package astpatch

import (
"bytes"
"fmt"
"go/ast"
"go/parser"
"go/token"
"testing"

"github.com/google/go-cmp/cmp"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"golang.org/x/tools/go/ast/astutil"
)

type patchSpy struct {
gotFuncs, gotStructs []string
}

const errorIfFuncName = "ErrorFuncName"

var errFuncName = fmt.Errorf("encountered sentinel function %q", errorIfFuncName)

func (s *patchSpy) funcRecorder(c *astutil.Cursor) error {
name := c.Node().(*ast.FuncDecl).Name.String()
if name == errorIfFuncName {
return errFuncName
}
s.gotFuncs = append(s.gotFuncs, name)
return nil
}

func (s *patchSpy) structRecorder(c *astutil.Cursor) error {
switch p := c.Parent().(type) {
case *ast.TypeSpec: // it's a `type x struct` not, for example, a `map[T]struct{}`
s.gotStructs = append(s.gotStructs, p.Name.String())
}
return nil
}

func TestPatchRegistry(t *testing.T) {
tests := []struct {
name string
src string
wantErr error
wantFuncs, wantStructs []string
}{
{
name: "happy path",
src: `package thepackage

func FnA(){}

func FnB(){}

type StructA struct{}

type StructB struct{}
`,
wantFuncs: []string{"FnA", "FnB"},
wantStructs: []string{"StructA", "StructB"},
},
{
name: "error propagation",
src: `package thepackage

func HappyFn() {}

func ` + errorIfFuncName + `() {}
`,
wantErr: errFuncName,
wantFuncs: []string{"HappyFn"},
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
var spy patchSpy
reg := make(PatchRegistry)

reg.Add("*", &ast.FuncDecl{}, spy.funcRecorder)
const pkgPath = `github.com/the/repo/thepackage`
reg.Add(pkgPath, &ast.StructType{}, spy.structRecorder)

reg.Add("unknown/package/path", &ast.FuncDecl{}, func(c *astutil.Cursor) error {
t.Errorf("unexpected call to %T with different package path", (Patch)(nil))
return nil
})

file := parseGoFile(t, token.NewFileSet(), tt.src)
bestEffortLogAST(t, file)

// None of the `require.Equal*()` variants provide a check for exact
// match (i.e. equivalent to ==) of the identical error being
// propagated.
if gotErr := reg.Apply(pkgPath, file); gotErr != tt.wantErr {
t.Fatalf("%T.Apply(...) got err %v; want %v", reg, gotErr, tt.wantErr)
}
assert.Empty(t, cmp.Diff(tt.wantFuncs, spy.gotFuncs), "encountered function declarations (-want +got)")
assert.Empty(t, cmp.Diff(tt.wantStructs, spy.gotStructs), "encountered struct-type declarations (-want +got)")
})
}
}

func parseGoFile(tb testing.TB, fset *token.FileSet, src string) *ast.File {
tb.Helper()
f, err := parser.ParseFile(fset, "", src, parser.SkipObjectResolution)
require.NoError(tb, err, "Parsing Go source as file: parser.ParseFile([see logged source])")
return f
}

func bestEffortLogAST(tb testing.TB, x any) {
tb.Helper()

var buf bytes.Buffer
if err := ast.Fprint(&buf, nil, x, nil); err != nil {
return
}
tb.Logf("AST of parsed source:\n\n%s", buf.String())
}
14 changes: 14 additions & 0 deletions x/gethclone/copyright.go.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// (c) 2019-2024, Ava Labs, Inc.
//
// This file is a derived work, based on the go-ethereum library whose original
// notices appear below.
//
// It is distributed under a license compatible with the licensing terms of the
// original code from which it is derived.
//
// Much love to the original authors for their work.
// **********
//
// Code generated by gethclone - DO NOT EDIT.
//
// **********
31 changes: 31 additions & 0 deletions x/gethclone/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
module github.com/ava-labs/subnet-evm/x/gethclone

go 1.22.4

require (
github.com/ava-labs/avalanchego v1.11.8
github.com/ethereum/go-ethereum v1.13.8
github.com/google/go-cmp v0.6.0
github.com/spf13/pflag v1.0.5
github.com/stretchr/testify v1.8.4
go.uber.org/multierr v1.10.0
golang.org/x/mod v0.18.0
golang.org/x/tools v0.22.0
)

require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/google/renameio/v2 v2.0.0 // indirect
github.com/gorilla/rpc v1.2.0 // indirect
github.com/holiman/uint256 v1.2.4 // indirect
github.com/mr-tron/base58 v1.2.0 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
go.uber.org/mock v0.4.0 // indirect
golang.org/x/crypto v0.21.0 // indirect
golang.org/x/exp v0.0.0-20231127185646-65229373498e // indirect
golang.org/x/sync v0.7.0 // indirect
golang.org/x/sys v0.21.0 // indirect
gonum.org/v1/gonum v0.11.0 // indirect
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
58 changes: 58 additions & 0 deletions x/gethclone/go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
github.com/ava-labs/avalanchego v1.11.8 h1:Q/der5bC/q3BQbIqxT7nNC0F30c+6X1G/eQzzMQ2CLk=
github.com/ava-labs/avalanchego v1.11.8/go.mod h1:aPYTETkM0KjtC7vFwPO6S8z2L0QTKaXjVUi98pTdNO4=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/ethereum/go-ethereum v1.13.8 h1:1od+thJel3tM52ZUNQwvpYOeRHlbkVFZ5S8fhi0Lgsg=
github.com/ethereum/go-ethereum v1.13.8/go.mod h1:sc48XYQxCzH3fG9BcrXCOOgQk2JfZzNAmIKnceogzsA=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/renameio/v2 v2.0.0 h1:UifI23ZTGY8Tt29JbYFiuyIU3eX+RNFtUwefq9qAhxg=
github.com/google/renameio/v2 v2.0.0/go.mod h1:BtmJXm5YlszgC+TD4HOEEUFgkJP3nLxehU6hfe7jRt4=
github.com/gorilla/rpc v1.2.0 h1:WvvdC2lNeT1SP32zrIce5l0ECBfbAlmrmSBsuc57wfk=
github.com/gorilla/rpc v1.2.0/go.mod h1:V4h9r+4sF5HnzqbwIez0fKSpANP0zlYd3qR7p36jkTQ=
github.com/holiman/uint256 v1.2.4 h1:jUc4Nk8fm9jZabQuqr2JzednajVmBpC+oiTiXZJEApU=
github.com/holiman/uint256 v1.2.4/go.mod h1:EOMSn4q6Nyt9P6efbI3bueV4e1b3dGlUCXeiRV4ng7E=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/mr-tron/base58 v1.2.0 h1:T/HDJBh4ZCPbU39/+c3rRvE0uKBQlU27+QI8LJ4t64o=
github.com/mr-tron/base58 v1.2.0/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
github.com/sanity-io/litter v1.5.1 h1:dwnrSypP6q56o3lFxTU+t2fwQ9A+U5qrXVO4Qg9KwVU=
github.com/sanity-io/litter v1.5.1/go.mod h1:5Z71SvaYy5kcGtyglXOC9rrUi3c1E8CamFWjQsazTh0=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/thepudds/fzgen v0.4.2 h1:HlEHl5hk2/cqEomf2uK5SA/FeJc12s/vIHmOG+FbACw=
github.com/thepudds/fzgen v0.4.2/go.mod h1:kHCWdsv5tdnt32NIHYDdgq083m6bMtaY0M+ipiO9xWE=
go.uber.org/mock v0.4.0 h1:VcM4ZOtdbR4f6VXfiOpwpVJDL6lCReaZ6mw31wqh7KU=
go.uber.org/mock v0.4.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc=
go.uber.org/multierr v1.10.0 h1:S0h4aNzvfcFsC3dRF1jLoaov7oRaKqRGC/pUEJ2yvPQ=
go.uber.org/multierr v1.10.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA=
golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
golang.org/x/exp v0.0.0-20231127185646-65229373498e h1:Gvh4YaCaXNs6dKTlfgismwWZKyjVZXwOPfIyUaqU3No=
golang.org/x/exp v0.0.0-20231127185646-65229373498e/go.mod h1:iRJReGqOEeBhDZGkGbynYwcHlctCvnjTYIamk7uXpHI=
golang.org/x/mod v0.18.0 h1:5+9lSbEzPSdWkH32vYPBwEpX8KwDbM52Ud9xBUvNlb0=
golang.org/x/mod v0.18.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws=
golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/tools v0.22.0 h1:gqSGLZqv+AI9lIQzniJ0nZDRG5GBPsSi+DRNHWNz6yA=
golang.org/x/tools v0.22.0/go.mod h1:aCwcsjqvq7Yqt6TNyX7QMU2enbQ/Gt0bo6krSeEri+c=
gonum.org/v1/gonum v0.11.0 h1:f1IJhK4Km5tBJmaiJXtk/PkL4cdVX6J+tGiM187uT5E=
gonum.org/v1/gonum v0.11.0/go.mod h1:fSG4YDCxxUZQJ7rKsQrj0gMOg00Il0Z96/qMA4bVQhA=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
Loading