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
}