chore: golangci-lint CI workflow (#16)

* chore: `golangci-lint` CI workflow

* fix: make `golangci-lint` happy

* chore: bump `actions/{checkout,setup-go}` versions

* chore: overhaul `.golanci.yml` config

* fix: all linter issues

* chore: exclude non-libevm linters + change deprecated option

* fix: add overflow check in example

* fix: try again; different local version?

* chore: this is trying my patience

* chore: enable `gci` and fix ordering

* chore: mark `ethclient/gethclient` test as flaky

* chore: mark `eth/catalyst` test as flaky
This commit is contained in:
Arran Schlosberg 2024-09-12 20:31:04 +01:00 committed by GitHub
parent 2d3894fb97
commit 04543ea837
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
16 changed files with 164 additions and 66 deletions

View file

@ -11,13 +11,13 @@ jobs:
go_test_short: go_test_short:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v4
- name: Set up Go - name: Set up Go
uses: actions/setup-go@v2 uses: actions/setup-go@v5
with: with:
go-version: 1.21.4 go-version: 1.21.4
- name: Run tests - name: Run tests
run: | # Upstream flakes are race conditions exacerbated by concurrent tests run: | # Upstream flakes are race conditions exacerbated by concurrent tests
FLAKY_REGEX='go-ethereum/(eth|accounts/keystore|eth/downloader|miner)$'; FLAKY_REGEX='go-ethereum/(eth|accounts/keystore|eth/downloader|miner|ethclient/gethclient|eth/catalyst)$';
go list ./... | grep -P "${FLAKY_REGEX}" | xargs -n 1 go test -short; go list ./... | grep -P "${FLAKY_REGEX}" | xargs -n 1 go test -short;
go test -short $(go list ./... | grep -Pv "${FLAKY_REGEX}"); go test -short $(go list ./... | grep -Pv "${FLAKY_REGEX}");

25
.github/workflows/golangci-lint.yml vendored Normal file
View file

@ -0,0 +1,25 @@
name: golangci-lint
on:
push:
branches: [ libevm ]
pull_request:
branches: [ libevm ]
workflow_dispatch:
permissions:
contents: read
jobs:
golangci:
name: lint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: stable
- name: golangci-lint
uses: golangci/golangci-lint-action@v6
with:
version: v1.60

View file

@ -3,58 +3,112 @@
run: run:
timeout: 20m timeout: 20m
tests: true tests: true
# default is true. Enables skipping of directories:
# vendor$, third_party$, testdata$, examples$, Godeps$, builtin$
skip-dirs-use-default: true
skip-files:
- core/genesis_alloc.go
linters: linters:
disable-all: true
enable: enable:
# Every available linter at the time of writing was considered (quickly) and
# inclusion was liberal. Linters are good at detecting code smells, but if
# we find that a particular one causes too many false positives then we can
# configure it better or, as a last resort, remove it.
- containedctx
- errcheck
- forcetypeassert
- gci
- gocheckcompilerdirectives
- gofmt
- goimports - goimports
- gosimple - gomodguard
- gosec
- govet - govet
- ineffassign - ineffassign
# TODO(arr4n): investigate ireturn
- misspell - misspell
- unconvert - nakedret
- typecheck - nestif
- unused - nilerr
- nolintlint
- reassign
- revive
- sloglint
- staticcheck - staticcheck
- bidichk - tagliatelle
- durationcheck - testableexamples
- exportloopref - testifylint
- thelper
- tparallel
- unconvert
- usestdlibvars
- unused
- whitespace - whitespace
# - structcheck # lots of false positives
# - errcheck #lot of false positives
# - contextcheck
# - errchkjson # lots of false positives
# - errorlint # this check crashes
# - exhaustive # silly check
# - makezero # false positives
# - nilerr # several intentional
linters-settings: linters-settings:
gofmt: gci:
simplify: true custom-order: true
sections:
- standard
- default
- localmodule
# The rest of these break developer expections, in increasing order of
# divergence, so are at the end to increase the chance of being seen.
- alias
- dot
- blank
gomodguard:
blocked:
modules:
- github.com/ava-labs/avalanchego:
- github.com/ava-labs/coreth:
- github.com/ava-labs/subnet-evm:
revive:
rules:
- name: unused-parameter
# Method parameters may be equired by interfaces and forcing them to be
# named _ is of questionable benefit.
disabled: true
issues: issues:
exclude-dirs-use-default: false
exclude-rules: exclude-rules:
- path: crypto/bn256/cloudflare/optate.go - path-except: libevm
linters: linters:
- deadcode # If any issue is flagged in a non-libevm file, add the linter here
# because the problem isn't under our control.
- containedctx
- forcetypeassert
- errcheck
- gci
- gofmt
- gosec
- gosimple
- govet
- nakedret
- nestif
- nilerr
- nolintlint
- revive
- staticcheck - staticcheck
- path: internal/build/pgp.go - tagliatelle
text: 'SA1019: "golang.org/x/crypto/openpgp" is deprecated: this package is unmaintained except for security fixes.' - testableexamples
- path: core/vm/contracts.go - testifylint
text: 'SA1019: "golang.org/x/crypto/ripemd160" is deprecated: RIPEMD-160 is a legacy hash and should not be used for new applications.' - thelper
- path: accounts/usbwallet/trezor.go - tparallel
text: 'SA1019: "github.com/golang/protobuf/proto" is deprecated: Use the "google.golang.org/protobuf/proto" package instead.' - usestdlibvars
- path: accounts/usbwallet/trezor/ - varnamelen
text: 'SA1019: "github.com/golang/protobuf/proto" is deprecated: Use the "google.golang.org/protobuf/proto" package instead.' - wastedassign
exclude: - whitespace
- 'SA1019: event.TypeMux is deprecated: use Feed' include:
- 'SA1019: strings.Title is deprecated' # Many of the default exclusions are because, verbatim "Annoying issue",
- 'SA1019: strings.Title has been deprecated since Go 1.18 and an alternative has been available since Go 1.0: The rule Title uses for word boundaries does not handle Unicode punctuation properly. Use golang.org/x/text/cases instead.' # which defeats the point of a linter.
- 'SA1029: should not use built-in type string as key for value' - EXC0002
- EXC0004
- EXC0005
- EXC0006
- EXC0007
- EXC0008
- EXC0009
- EXC0010
- EXC0011
- EXC0012
- EXC0013
- EXC0014
- EXC0015

View file

@ -4,12 +4,13 @@ import (
"fmt" "fmt"
"testing" "testing"
"github.com/stretchr/testify/require"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/libevm" "github.com/ethereum/go-ethereum/libevm"
"github.com/ethereum/go-ethereum/libevm/ethtest" "github.com/ethereum/go-ethereum/libevm/ethtest"
"github.com/ethereum/go-ethereum/libevm/hookstest" "github.com/ethereum/go-ethereum/libevm/hookstest"
"github.com/stretchr/testify/require"
) )
func TestCanExecuteTransaction(t *testing.T) { func TestCanExecuteTransaction(t *testing.T) {

View file

@ -3,9 +3,10 @@ package vm
import ( import (
"fmt" "fmt"
"github.com/holiman/uint256"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/params"
"github.com/holiman/uint256"
) )
// evmCallArgs mirrors the parameters of the [EVM] methods Call(), CallCode(), // evmCallArgs mirrors the parameters of the [EVM] methods Call(), CallCode(),

View file

@ -4,6 +4,11 @@ import (
"fmt" "fmt"
"testing" "testing"
"github.com/holiman/uint256"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"golang.org/x/exp/rand"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto"
@ -11,10 +16,6 @@ import (
"github.com/ethereum/go-ethereum/libevm/ethtest" "github.com/ethereum/go-ethereum/libevm/ethtest"
"github.com/ethereum/go-ethereum/libevm/hookstest" "github.com/ethereum/go-ethereum/libevm/hookstest"
"github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/params"
"github.com/holiman/uint256"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"golang.org/x/exp/rand"
) )
type precompileStub struct { type precompileStub struct {

View file

@ -5,13 +5,14 @@ package ethtest
import ( import (
"testing" "testing"
"github.com/stretchr/testify/require"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/params"
"github.com/stretchr/testify/require"
) )
// NewZeroEVM returns a new EVM backed by a [rawdb.NewMemoryDatabase]; all other // NewZeroEVM returns a new EVM backed by a [rawdb.NewMemoryDatabase]; all other

View file

@ -1,8 +1,9 @@
package ethtest package ethtest
import ( import (
"github.com/ethereum/go-ethereum/common"
"golang.org/x/exp/rand" "golang.org/x/exp/rand"
"github.com/ethereum/go-ethereum/common"
) )
// PseudoRand extends [rand.Rand] (*not* crypto/rand). // PseudoRand extends [rand.Rand] (*not* crypto/rand).
@ -17,7 +18,7 @@ func NewPseudoRand(seed uint64) *PseudoRand {
// Address returns a pseudorandom address. // Address returns a pseudorandom address.
func (r *PseudoRand) Address() (a common.Address) { func (r *PseudoRand) Address() (a common.Address) {
r.Read(a[:]) r.Read(a[:]) //nolint:gosec,errcheck // Guaranteed nil error
return a return a
} }
@ -29,13 +30,13 @@ func (r *PseudoRand) AddressPtr() *common.Address {
// Hash returns a pseudorandom hash. // Hash returns a pseudorandom hash.
func (r *PseudoRand) Hash() (h common.Hash) { func (r *PseudoRand) Hash() (h common.Hash) {
r.Read(h[:]) r.Read(h[:]) //nolint:gosec,errcheck // Guaranteed nil error
return h return h
} }
// Bytes returns `n` pseudorandom bytes. // Bytes returns `n` pseudorandom bytes.
func (r *PseudoRand) Bytes(n uint) []byte { func (r *PseudoRand) Bytes(n uint) []byte {
b := make([]byte, n) b := make([]byte, n)
r.Read(b) r.Read(b) //nolint:gosec,errcheck // Guaranteed nil error
return b return b
} }

View file

@ -12,9 +12,10 @@ import (
) )
// Register clears any registered [params.Extras] and then registers `extras` // Register clears any registered [params.Extras] and then registers `extras`
// for the liftime of the current test, clearing them via tb's // for the lifetime of the current test, clearing them via tb's
// [testing.TB.Cleanup]. // [testing.TB.Cleanup].
func Register[C params.ChainConfigHooks, R params.RulesHooks](tb testing.TB, extras params.Extras[C, R]) { func Register[C params.ChainConfigHooks, R params.RulesHooks](tb testing.TB, extras params.Extras[C, R]) {
tb.Helper()
params.TestOnlyClearRegisteredExtras() params.TestOnlyClearRegisteredExtras()
tb.Cleanup(params.TestOnlyClearRegisteredExtras) tb.Cleanup(params.TestOnlyClearRegisteredExtras)
params.RegisterExtras(extras) params.RegisterExtras(extras)
@ -32,6 +33,7 @@ type Stub struct {
// Register is a convenience wrapper for registering s as both the // Register is a convenience wrapper for registering s as both the
// [params.ChainConfigHooks] and [params.RulesHooks] via [Register]. // [params.ChainConfigHooks] and [params.RulesHooks] via [Register].
func (s *Stub) Register(tb testing.TB) { func (s *Stub) Register(tb testing.TB) {
tb.Helper()
Register(tb, params.Extras[*Stub, *Stub]{ Register(tb, params.Extras[*Stub, *Stub]{
NewRules: func(_ *params.ChainConfig, _ *params.Rules, _ *Stub, blockNum *big.Int, isMerge bool, timestamp uint64) *Stub { NewRules: func(_ *params.ChainConfig, _ *params.Rules, _ *Stub, blockNum *big.Int, isMerge bool, timestamp uint64) *Stub {
return s return s
@ -39,6 +41,9 @@ func (s *Stub) Register(tb testing.TB) {
}) })
} }
// PrecompileOverride uses the s.PrecompileOverrides map, if non-empty, as the
// canonical source of all overrides. If the map is empty then no precompiles
// are overridden.
func (s Stub) PrecompileOverride(a common.Address) (libevm.PrecompiledContract, bool) { func (s Stub) PrecompileOverride(a common.Address) (libevm.PrecompiledContract, bool) {
if len(s.PrecompileOverrides) == 0 { if len(s.PrecompileOverrides) == 0 {
return nil, false return nil, false
@ -47,6 +52,8 @@ func (s Stub) PrecompileOverride(a common.Address) (libevm.PrecompiledContract,
return p, ok return p, ok
} }
// CanExecuteTransaction proxies arguments to the s.CanExecuteTransactionFn
// function if non-nil, otherwise it acts as a noop.
func (s Stub) CanExecuteTransaction(from common.Address, to *common.Address, sr libevm.StateReader) error { func (s Stub) CanExecuteTransaction(from common.Address, to *common.Address, sr libevm.StateReader) error {
if f := s.CanExecuteTransactionFn; f != nil { if f := s.CanExecuteTransactionFn; f != nil {
return f(from, to, sr) return f(from, to, sr)
@ -54,6 +61,8 @@ func (s Stub) CanExecuteTransaction(from common.Address, to *common.Address, sr
return nil return nil
} }
// CanCreateContract proxies arguments to the s.CanCreateContractFn function if
// non-nil, otherwise it acts as a noop.
func (s Stub) CanCreateContract(cc *libevm.AddressContext, sr libevm.StateReader) error { func (s Stub) CanCreateContract(cc *libevm.AddressContext, sr libevm.StateReader) error {
if f := s.CanCreateContractFn; f != nil { if f := s.CanCreateContractFn; f != nil {
return f(cc, sr) return f(cc, sr)

View file

@ -1,8 +1,9 @@
package libevm package libevm
import ( import (
"github.com/ethereum/go-ethereum/common"
"github.com/holiman/uint256" "github.com/holiman/uint256"
"github.com/ethereum/go-ethereum/common"
) )
// PrecompiledContract is an exact copy of vm.PrecompiledContract, mirrored here // PrecompiledContract is an exact copy of vm.PrecompiledContract, mirrored here

View file

@ -14,6 +14,7 @@ func TestConstructor(t *testing.T) {
testConstructor[struct{ x string }](t) testConstructor[struct{ x string }](t)
} }
//nolint:thelper // This is the test itself so we want local line numbers reported.
func testConstructor[T any](t *testing.T) { func testConstructor[T any](t *testing.T) {
var zero T var zero T
t.Run(fmt.Sprintf("%T", zero), func(t *testing.T) { t.Run(fmt.Sprintf("%T", zero), func(t *testing.T) {

View file

@ -106,10 +106,10 @@ func MustNewValue[T any](t *Type) *Value[T] {
} }
// Get returns the value. // Get returns the value.
func (a *Value[T]) Get() T { return a.t.val.get().(T) } func (v *Value[T]) Get() T { return v.t.val.get().(T) } //nolint:forcetypeassert // invariant
// Set sets the value. // Set sets the value.
func (a *Value[T]) Set(v T) { a.t.val.mustSet(v) } func (v *Value[T]) Set(val T) { v.t.val.mustSet(val) }
// MarshalJSON implements the [json.Marshaler] interface. // MarshalJSON implements the [json.Marshaler] interface.
func (t *Type) MarshalJSON() ([]byte, error) { return t.val.MarshalJSON() } func (t *Type) MarshalJSON() ([]byte, error) { return t.val.MarshalJSON() }

View file

@ -24,6 +24,7 @@ func TestType(t *testing.T) {
testType(t, "nil pointer", Zero[*float64], (*float64)(nil), new(float64), 0) testType(t, "nil pointer", Zero[*float64], (*float64)(nil), new(float64), 0)
} }
//nolint:thelper // This is the test itself so we want local line numbers reported.
func testType[T any](t *testing.T, name string, ctor func() *Pseudo[T], init T, setTo T, invalid any) { func testType[T any](t *testing.T, name string, ctor func() *Pseudo[T], init T, setTo T, invalid any) {
t.Run(name, func(t *testing.T) { t.Run(name, func(t *testing.T) {
typ, val := ctor().TypeAndValue() typ, val := ctor().TypeAndValue()
@ -67,6 +68,7 @@ func testType[T any](t *testing.T, name string, ctor func() *Pseudo[T], init T,
}) })
} }
//nolint:ineffassign,testableexamples // Although `typ` is overwritten it's to demonstrate different approaches
func ExamplePseudo_TypeAndValue() { func ExamplePseudo_TypeAndValue() {
typ, val := From("hello").TypeAndValue() typ, val := From("hello").TypeAndValue()
@ -98,5 +100,4 @@ func TestPointer(t *testing.T) {
ptr.payload = 314159 ptr.payload = 314159
assert.Equal(t, 314159, val.Get().payload, "after setting via pointer") assert.Equal(t, 314159, val.Get().payload, "after setting via pointer")
}) })
} }

View file

@ -5,9 +5,10 @@ import (
"math/big" "math/big"
"testing" "testing"
"github.com/ethereum/go-ethereum/libevm/pseudo"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"github.com/ethereum/go-ethereum/libevm/pseudo"
) )
type rawJSON struct { type rawJSON struct {
@ -102,18 +103,18 @@ func TestRegisterExtras(t *testing.T) {
tt.register() tt.register()
defer TestOnlyClearRegisteredExtras() defer TestOnlyClearRegisteredExtras()
in := &ChainConfig{ input := &ChainConfig{
ChainID: big.NewInt(142857), ChainID: big.NewInt(142857),
extra: tt.ccExtra, extra: tt.ccExtra,
} }
buf, err := json.Marshal(in) buf, err := json.Marshal(input)
require.NoError(t, err) require.NoError(t, err)
got := new(ChainConfig) got := new(ChainConfig)
require.NoError(t, json.Unmarshal(buf, got)) require.NoError(t, json.Unmarshal(buf, got))
assert.Equal(t, tt.ccExtra.Interface(), got.extraPayload().Interface()) assert.Equal(t, tt.ccExtra.Interface(), got.extraPayload().Interface())
assert.Equal(t, in, got) assert.Equal(t, input, got)
gotRules := got.Rules(nil, false, 0) gotRules := got.Rules(nil, false, 0)
assert.Equal(t, tt.wantRulesExtra, gotRules.extraPayload().Interface()) assert.Equal(t, tt.wantRulesExtra, gotRules.extraPayload().Interface())

View file

@ -106,8 +106,8 @@ func (r RulesExtra) PrecompileOverride(addr common.Address) (_ libevm.Precompile
// to state allows it to be configured on-chain however this is an optional // to state allows it to be configured on-chain however this is an optional
// implementation detail. // implementation detail.
func (r RulesExtra) CanCreateContract(*libevm.AddressContext, libevm.StateReader) error { func (r RulesExtra) CanCreateContract(*libevm.AddressContext, libevm.StateReader) error {
if time.Unix(int64(r.timestamp), 0).UTC().Day() != int(time.Tuesday) { if time.Unix(int64(r.timestamp), 0).UTC().Day() != int(time.Tuesday) { //nolint:gosec // G115 timestamp won't overflow int64 for millions of years so this is someone else's problem
return errors.New("uh oh!") return errors.New("uh oh")
} }
return nil return nil
} }

View file

@ -6,8 +6,9 @@ import (
"math/big" "math/big"
"testing" "testing"
"github.com/ethereum/go-ethereum/libevm/pseudo"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"github.com/ethereum/go-ethereum/libevm/pseudo"
) )
type nestedChainConfigExtra struct { type nestedChainConfigExtra struct {