diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index 701fe6b4cd2..47e20c549fd 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -22,7 +22,7 @@ jobs: go_test_short: env: - FLAKY_REGEX: "ava-labs/libevm/(triedb/pathdb|eth|eth/tracers/js|eth/tracers/logger|accounts/abi/bind|accounts/keystore|eth/downloader|miner|ethclient|ethclient/gethclient|eth/catalyst)$" + FLAKY_REGEX: "ava-labs/libevm/(triedb/pathdb|eth|eth/tracers/js|eth/tracers/logger|eth/tracers/internal/tracetest|accounts/abi/bind|accounts/keystore|eth/downloader|miner|ethclient|ethclient/gethclient|eth/catalyst)$" runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 diff --git a/core/state_transition.libevm.go b/core/state_transition.libevm.go index 49e509d5637..701cc4d5ee7 100644 --- a/core/state_transition.libevm.go +++ b/core/state_transition.libevm.go @@ -16,10 +16,24 @@ package core +import ( + "github.com/ava-labs/libevm/log" +) + // canExecuteTransaction is a convenience wrapper for calling the // [params.RulesHooks.CanExecuteTransaction] hook. func (st *StateTransition) canExecuteTransaction() error { bCtx := st.evm.Context rules := st.evm.ChainConfig().Rules(bCtx.BlockNumber, bCtx.Random != nil, bCtx.Time) - return rules.Hooks().CanExecuteTransaction(st.msg.From, st.msg.To, st.state) + if err := rules.Hooks().CanExecuteTransaction(st.msg.From, st.msg.To, st.state); err != nil { + log.Debug( + "Transaction execution blocked by libevm hook", + "from", st.msg.From, + "to", st.msg.To, + "hooks", log.TypeOf(rules.Hooks()), + "reason", err, + ) + return err + } + return nil } diff --git a/core/types/rlp_payload.libevm.go b/core/types/rlp_payload.libevm.go index 98aebe1cc4b..ff3530d1692 100644 --- a/core/types/rlp_payload.libevm.go +++ b/core/types/rlp_payload.libevm.go @@ -23,6 +23,7 @@ import ( "github.com/ava-labs/libevm/libevm/pseudo" "github.com/ava-labs/libevm/libevm/register" "github.com/ava-labs/libevm/libevm/testonly" + "github.com/ava-labs/libevm/log" "github.com/ava-labs/libevm/rlp" ) @@ -84,6 +85,12 @@ func RegisterExtras[ newStateAccount: pseudo.NewConstructor[SA]().Zero, hooks: extra, }) + log.Info( + "Registered core/types extras", + "Header", log.TypeOf(pseudo.Zero[HPtr]().Value.Get()), + "Block/Body", log.TypeOf(pseudo.Zero[BPtr]().Value.Get()), + "StateAccount", log.TypeOf(pseudo.Zero[SA]().Value.Get()), + ) return extra } diff --git a/core/vm/contracts.go b/core/vm/contracts.go index a869debebba..ff1f3a0ca79 100644 --- a/core/vm/contracts.go +++ b/core/vm/contracts.go @@ -148,10 +148,7 @@ func init() { } // ActivePrecompiles returns the precompiles enabled with the current configuration. -func ActivePrecompiles(rules params.Rules) (active []common.Address) { - defer func() { - active = rules.Hooks().ActivePrecompiles(append([]common.Address{}, active...)) - }() +func activePrecompiles(rules params.Rules) []common.Address { switch { case rules.IsCancun: return PrecompiledAddressesCancun diff --git a/core/vm/contracts.libevm.go b/core/vm/contracts.libevm.go index 66e3c91783a..12271874602 100644 --- a/core/vm/contracts.libevm.go +++ b/core/vm/contracts.libevm.go @@ -21,13 +21,42 @@ import ( "math/big" "github.com/holiman/uint256" + "golang.org/x/exp/slog" "github.com/ava-labs/libevm/common" "github.com/ava-labs/libevm/core/types" "github.com/ava-labs/libevm/libevm" + "github.com/ava-labs/libevm/libevm/set" + "github.com/ava-labs/libevm/log" "github.com/ava-labs/libevm/params" ) +// ActivePrecompiles returns the precompiles enabled with the current configuration. +func ActivePrecompiles(rules params.Rules) []common.Address { + orig := activePrecompiles(rules) // original, upstream implementation + active := rules.Hooks().ActivePrecompiles(append([]common.Address{}, orig...)) + + // As all set computation is done lazily and only when debugging, there is + // some duplication in favour of simplified code. + log.Debug( + "Overriding active precompiles", + "added", log.Lazy(func() slog.Value { + diff := set.From(active...).Sub(set.From(orig...)) + return slog.AnyValue(diff.Slice()) + }), + "removed", log.Lazy(func() slog.Value { + diff := set.From(orig...).Sub(set.From(active...)) + return slog.AnyValue(diff.Slice()) + }), + "unchanged", log.Lazy(func() slog.Value { + both := set.From(active...).Intersect(set.From(orig...)) + return slog.AnyValue(both.Slice()) + }), + ) + + return active +} + // evmCallArgs mirrors the parameters of the [EVM] methods Call(), CallCode(), // DelegateCall() and StaticCall(). Its fields are identical to those of the // parameters, prepended with the receiver name and call type. As diff --git a/core/vm/evm.go b/core/vm/evm.go index ae56d5a69ed..ca3f5d21037 100644 --- a/core/vm/evm.go +++ b/core/vm/evm.go @@ -23,7 +23,7 @@ import ( "github.com/ava-labs/libevm/common" "github.com/ava-labs/libevm/core/types" "github.com/ava-labs/libevm/crypto" - "github.com/ava-labs/libevm/libevm" + "github.com/ava-labs/libevm/log" "github.com/ava-labs/libevm/params" "github.com/holiman/uint256" ) @@ -40,6 +40,7 @@ type ( func (evm *EVM) precompile(addr common.Address) (PrecompiledContract, bool) { if p, override := evm.chainRules.Hooks().PrecompileOverride(addr); override { + log.Debug("Overriding precompile", "address", addr, "implementation", log.TypeOf(p)) return p, p != nil } var precompiles map[common.Address]PrecompiledContract @@ -459,8 +460,7 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64, // This check MUST be placed after the caller's nonce is incremented but // before all other state-modifying behaviour, even if changes may be // reverted to the snapshot. - addrs := &libevm.AddressContext{Origin: evm.Origin, Caller: caller.Address(), Self: address} - gas, err := evm.chainRules.Hooks().CanCreateContract(addrs, gas, evm.StateDB) + gas, err := evm.canCreateContract(caller, address, gas) if err != nil { return nil, common.Address{}, gas, err } diff --git a/core/vm/evm.libevm.go b/core/vm/evm.libevm.go new file mode 100644 index 00000000000..48278c28c37 --- /dev/null +++ b/core/vm/evm.libevm.go @@ -0,0 +1,45 @@ +// Copyright 2025 the libevm authors. +// +// The libevm additions to go-ethereum are free software: you can redistribute +// them and/or modify them under the terms of the GNU Lesser General Public License +// as published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The libevm additions are distributed in the hope that they will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser +// General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see +// . + +package vm + +import ( + "github.com/ava-labs/libevm/common" + "github.com/ava-labs/libevm/libevm" + "github.com/ava-labs/libevm/log" +) + +// canCreateContract is a convenience wrapper for calling the +// [params.RulesHooks.CanCreateContract] hook. +func (evm *EVM) canCreateContract(caller ContractRef, contractToCreate common.Address, gas uint64) (remainingGas uint64, _ error) { + addrs := &libevm.AddressContext{Origin: evm.Origin, Caller: caller.Address(), Self: contractToCreate} + gas, err := evm.chainRules.Hooks().CanCreateContract(addrs, gas, evm.StateDB) + + // NOTE that this block only performs logging and that all paths propagate + // `(gas, err)` unmodified. + if err != nil { + log.Debug( + "Contract creation blocked by libevm hook", + "origin", addrs.Origin, + "caller", addrs.Caller, + "contract", addrs.Self, + "hooks", log.TypeOf(evm.chainRules.Hooks()), + "reason", err, + ) + } + + return gas, err +} diff --git a/libevm/set/set.go b/libevm/set/set.go new file mode 100644 index 00000000000..4f055434e19 --- /dev/null +++ b/libevm/set/set.go @@ -0,0 +1,59 @@ +// Copyright 2025 the libevm authors. +// +// The libevm additions to go-ethereum are free software: you can redistribute +// them and/or modify them under the terms of the GNU Lesser General Public License +// as published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The libevm additions are distributed in the hope that they will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser +// General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see +// . + +// Package set provides a generic implementation of a set. +package set + +// A Set is a generic set implementation. +type Set[T comparable] map[T]struct{} + +// From returns a Set containing the elements. +func From[T comparable](elements ...T) Set[T] { + s := make(Set[T], len(elements)) + for _, e := range elements { + s[e] = struct{}{} + } + return s +} + +// Sub returns the elements in `s` that aren't in `t`. +func (s Set[T]) Sub(t Set[T]) Set[T] { + return s.alsoIn(t, false) +} + +// Intersect returns the intersection of `s` and `t`. +func (s Set[T]) Intersect(t Set[T]) Set[T] { + return s.alsoIn(t, true) +} + +func (s Set[T]) alsoIn(t Set[T], inBoth bool) Set[T] { + res := make(Set[T]) + for el := range s { + if _, ok := t[el]; ok == inBoth { + res[el] = struct{}{} + } + } + return res +} + +// Slice returns the elements of `s` as a slice. +func (s Set[T]) Slice() []T { + sl := make([]T, 0, len(s)) + for el := range s { + sl = append(sl, el) + } + return sl +} diff --git a/libevm/set/set_test.go b/libevm/set/set_test.go new file mode 100644 index 00000000000..888dbbe3d0e --- /dev/null +++ b/libevm/set/set_test.go @@ -0,0 +1,56 @@ +// Copyright 2025 the libevm authors. +// +// The libevm additions to go-ethereum are free software: you can redistribute +// them and/or modify them under the terms of the GNU Lesser General Public License +// as published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The libevm additions are distributed in the hope that they will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser +// General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see +// . + +package set + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestSub(t *testing.T) { + for _, tt := range [][3][]int{ // start, sub, want + {{}, {}, {}}, + {{0}, {}, {0}}, + {{}, {0}, {}}, + {{0, 1}, {0}, {1}}, + {{0, 1}, {1}, {0}}, + } { + in, sub := tt[0], tt[1] + want := tt[2] + got := From(in...).Sub(From(sub...)).Slice() + assert.Equalf(t, want, got, "Set(%v).Sub(%v)", in, sub) + } +} + +func TestIntersect(t *testing.T) { + for _, tt := range [][3][]int{ // L, R, intersection + {{}, {}, {}}, + {{0}, {}, {}}, + {{0}, {0}, {0}}, + {{0, 1}, {0}, {0}}, + {{0, 1}, {1}, {1}}, + } { + want := tt[2] + + for i := 0; i <= 1; i++ { // commutativity + lhs, rhs := tt[i], tt[1-i] + got := From(lhs...).Intersect(From(rhs...)).Slice() + assert.Equalf(t, want, got, "Set(%v).Intersect(%v)", lhs, rhs) + } + } +} diff --git a/log/value.libevm.go b/log/value.libevm.go new file mode 100644 index 00000000000..8c4ab0aaf36 --- /dev/null +++ b/log/value.libevm.go @@ -0,0 +1,41 @@ +// Copyright 2025 the libevm authors. +// +// The libevm additions to go-ethereum are free software: you can redistribute +// them and/or modify them under the terms of the GNU Lesser General Public License +// as published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The libevm additions are distributed in the hope that they will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser +// General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see +// . + +package log + +import ( + "fmt" + + "golang.org/x/exp/slog" +) + +// A Lazy function defers its execution until logging is performed. +type Lazy func() slog.Value + +var _ slog.LogValuer = Lazy(nil) + +// LogValue implements the [slog.LogValuer] interface. +func (l Lazy) LogValue() slog.Value { + return l() +} + +// TypeOf returns a Lazy function that reports the concrete type of `v` as +// determined with the `%T` [fmt] verb. +func TypeOf(v any) Lazy { + return Lazy(func() slog.Value { + return slog.StringValue(fmt.Sprintf("%T", v)) + }) +} diff --git a/log/value.libevm_test.go b/log/value.libevm_test.go new file mode 100644 index 00000000000..c8f15d46b30 --- /dev/null +++ b/log/value.libevm_test.go @@ -0,0 +1,67 @@ +// Copyright 2025 the libevm authors. +// +// The libevm additions to go-ethereum are free software: you can redistribute +// them and/or modify them under the terms of the GNU Lesser General Public License +// as published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The libevm additions are distributed in the hope that they will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser +// General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see +// . + +package log + +import ( + "bytes" + "testing" + + "github.com/stretchr/testify/assert" + "golang.org/x/exp/slog" +) + +func TestTypeOf(t *testing.T) { + type foo struct{} + + tests := map[any]string{ + nil: "", + int(0): "int", + int(1): "int", + uint(0): "uint", + foo{}: "log.foo", + (*foo)(nil): "*log.foo", + } + + for in, want := range tests { + got := TypeOf(in).LogValue() + assert.Equalf(t, want, got.String(), "TypeOf(%T(%[1]v))", in, in) + } +} + +func TestLazy(t *testing.T) { + const ( + key = "theKey" + val = "theVal" + wantLogged = key + "=" + val + ) + + var gotNumEvaluations int + fn := Lazy(func() slog.Value { + gotNumEvaluations++ + return slog.StringValue(val) + }) + + var out bytes.Buffer + log := slog.New(slog.NewTextHandler(&out, &slog.HandlerOptions{ + Level: slog.LevelInfo, + })) + log.Info("", key, fn) + log.Debug("", "not evaluated", fn) + + assert.Containsf(t, out.String(), wantLogged, "evaluation of %T function is logged", fn) + assert.Equalf(t, 1, gotNumEvaluations, "number of evaluations of %T function", fn) +} diff --git a/params/config.libevm.go b/params/config.libevm.go index f8a153f3d98..127f5febef0 100644 --- a/params/config.libevm.go +++ b/params/config.libevm.go @@ -23,6 +23,7 @@ import ( "github.com/ava-labs/libevm/libevm/pseudo" "github.com/ava-labs/libevm/libevm/register" + "github.com/ava-labs/libevm/log" ) // Extras are arbitrary payloads to be added as extra fields in [ChainConfig] @@ -79,6 +80,12 @@ func RegisterExtras[C ChainConfigHooks, R RulesHooks](e Extras[C, R]) ExtraPaylo newForRules: e.newForRules, payloads: payloads, }) + log.Info( + "Registered params extras", + "ChainConfig", log.TypeOf(pseudo.Zero[C]().Value.Get()), + "Rules", log.TypeOf(pseudo.Zero[R]().Value.Get()), + "ReuseJSONRoot", e.ReuseJSONRoot, + ) return payloads }