From 2a5e6308e0abcdfb972cf5baa5b4cedf1f4f8e6d Mon Sep 17 00:00:00 2001 From: Arran Schlosberg Date: Tue, 7 Oct 2025 15:48:26 +0100 Subject: [PATCH 1/6] refactor: temporary extras require proof of lock --- libevm/extraslock.go | 63 +++++++++++++++++++++++++++++++++ libevm/extraslock_test.go | 47 +++++++++++++++++++++++++ libevm/temporary/temporary.go | 65 ----------------------------------- params/config.libevm.go | 14 ++++++-- params/config.libevm_test.go | 28 +++++++++------ 5 files changed, 138 insertions(+), 79 deletions(-) create mode 100644 libevm/extraslock.go create mode 100644 libevm/extraslock_test.go delete mode 100644 libevm/temporary/temporary.go diff --git a/libevm/extraslock.go b/libevm/extraslock.go new file mode 100644 index 00000000000..32db70bdb1b --- /dev/null +++ b/libevm/extraslock.go @@ -0,0 +1,63 @@ +// 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 libevm + +import ( + "errors" + "sync" + "sync/atomic" +) + +var ( + extrasMu sync.Mutex + extrasHandle atomic.Uint64 +) + +// WithTemporaryExtrasLock takes a global lock and calls `fn` with a handle that +// can be used to prove that the lock is held. All package-specific temporary +// overrides require this proof. +// +// WithTemporaryExtrasLock MUST NOT be used on a live chain. It is solely +// intended for off-chain consumers that require access to extras. +func WithTemporaryExtrasLock(fn func(lock ExtrasLock) error) error { + extrasMu.Lock() + defer func() { + extrasHandle.Add(1) + extrasMu.Unlock() + }() + + v := extrasHandle.Load() + return fn(ExtrasLock{&v}) +} + +// ErrExpiredExtrasLock is returned by [ExtrasLock.Verify] if the lock has been +// persisted beyond the call to [WithTemporaryExtrasLock] that created it. +var ErrExpiredExtrasLock = errors.New("libevm.ExtrasLock expired") + +// An ExtrasLock is a handle that proves a current call to +// [WithTemporaryExtrasLock]. +type ExtrasLock struct { + handle *uint64 +} + +// Verify verifies that the lock is valid. +func (l ExtrasLock) Verify() error { + if *l.handle != extrasHandle.Load() { + return ErrExpiredExtrasLock + } + return nil +} diff --git a/libevm/extraslock_test.go b/libevm/extraslock_test.go new file mode 100644 index 00000000000..c5ae998f233 --- /dev/null +++ b/libevm/extraslock_test.go @@ -0,0 +1,47 @@ +// 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 libevm_test + +import ( + "testing" + + "github.com/stretchr/testify/assert" + + // Testing from outside the package to guarantee usage of the public API only. + . "github.com/ava-labs/libevm/libevm" +) + +func TestExtrasLock(t *testing.T) { + var zero ExtrasLock + assert.Panics(t, func() { zero.Verify() }, "Verify() method of zero-value ExtrasLock{}") + + assert.NoError(t, + WithTemporaryExtrasLock((ExtrasLock).Verify), + "WithTemporaryExtrasLock((ExtrasLock).Verify)", + ) + + var persisted ExtrasLock + WithTemporaryExtrasLock(func(l ExtrasLock) error { + persisted = l + return nil + }) + + assert.ErrorIs( + t, persisted.Verify(), ErrExpiredExtrasLock, + "Verify() of persisted ExtrasLock", + ) +} diff --git a/libevm/temporary/temporary.go b/libevm/temporary/temporary.go deleted file mode 100644 index dae3a5b772f..00000000000 --- a/libevm/temporary/temporary.go +++ /dev/null @@ -1,65 +0,0 @@ -// 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 temporary provides thread-safe, temporary registration of all libevm -// hooks and payloads. -package temporary - -import ( - "sync" - - "github.com/ava-labs/libevm/core/state" - "github.com/ava-labs/libevm/core/types" - "github.com/ava-labs/libevm/core/vm" - "github.com/ava-labs/libevm/params" -) - -var mu sync.Mutex - -// WithRegisteredExtras takes a global lock and temporarily registers [params], -// [state], [types], and [vm] extras before calling the provided function. It -// can be thought of as an atomic call to all functions equivalent to -// [params.WithTempRegisteredExtras]. -// -// This is the *only* safe way to override libevm functionality. Direct calls to -// the package-specific temporary registration functions are not advised. -// -// WithRegisteredExtras MUST NOT be used on a live chain. It is solely intended -// for off-chain consumers that require access to extras. -func WithRegisteredExtras[ - C params.ChainConfigHooks, R params.RulesHooks, - H, B, SA any, - HPtr types.HeaderHooksPointer[H], - BPtr types.BlockBodyHooksPointer[B, BPtr], -]( - paramsExtras params.Extras[C, R], - sdbHooks state.StateDBHooks, - vmHooks vm.Hooks, - fn func(params.ExtraPayloads[C, R], types.ExtraPayloads[HPtr, BPtr, SA]), -) { - mu.Lock() - defer mu.Unlock() - - params.WithTempRegisteredExtras(paramsExtras, func(paramsPayloads params.ExtraPayloads[C, R]) { - types.WithTempRegisteredExtras(func(typesPayloads types.ExtraPayloads[HPtr, BPtr, SA]) { - state.WithTempRegisteredExtras(sdbHooks, func() { - vm.WithTempRegisteredHooks(vmHooks, func() { - fn(paramsPayloads, typesPayloads) - }) - }) - }) - }) -} diff --git a/params/config.libevm.go b/params/config.libevm.go index 7d05893bb1b..02bc062db28 100644 --- a/params/config.libevm.go +++ b/params/config.libevm.go @@ -21,6 +21,7 @@ import ( "math/big" "reflect" + "github.com/ava-labs/libevm/libevm" "github.com/ava-labs/libevm/libevm/pseudo" "github.com/ava-labs/libevm/libevm/register" "github.com/ava-labs/libevm/log" @@ -106,11 +107,18 @@ func payloadsAndConstructors[C ChainConfigHooks, R RulesHooks](e Extras[C, R]) ( // call this function directly. Use the libevm/temporary.WithRegisteredExtras() // function instead as it atomically overrides all possible packages. func WithTempRegisteredExtras[C ChainConfigHooks, R RulesHooks]( + lock libevm.ExtrasLock, e Extras[C, R], - fn func(ExtraPayloads[C, R]), -) { + fn func(ExtraPayloads[C, R]) error, +) error { + if err := lock.Verify(); err != nil { + return err + } + payloads, ctors := payloadsAndConstructors(e) - registeredExtras.TempOverride(ctors, func() { fn(payloads) }) + var err error + registeredExtras.TempOverride(ctors, func() { err = fn(payloads) }) + return err } // TestOnlyClearRegisteredExtras clears the [Extras] previously passed to diff --git a/params/config.libevm_test.go b/params/config.libevm_test.go index da6f04ce4ef..a9b82b446f9 100644 --- a/params/config.libevm_test.go +++ b/params/config.libevm_test.go @@ -1,4 +1,4 @@ -// Copyright 2024 the libevm authors. +// Copyright 2024-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 @@ -13,6 +13,7 @@ // You should have received a copy of the GNU Lesser General Public License // along with the go-ethereum library. If not, see // . + package params import ( @@ -23,6 +24,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/ava-labs/libevm/libevm" "github.com/ava-labs/libevm/libevm/pseudo" "github.com/ava-labs/libevm/libevm/register" ) @@ -329,16 +331,20 @@ func TestTempRegisteredExtras(t *testing.T) { t.Run("before_temp", testPrimaryExtras) t.Run("WithTempRegisteredExtras", func(t *testing.T) { - WithTempRegisteredExtras( - override, - func(extras ExtraPayloads[overrideCC, overrideRules]) { // deliberately shadow `extras` - assertRulesCopiedFromChainConfig( - t, extras, "hello, world", - func(cc *overrideCC, x string) { cc.X = x }, - func(r *overrideRules) string { return r.X }, - ) - }, - ) + err := libevm.WithTemporaryExtrasLock(func(lock libevm.ExtrasLock) error { + return WithTempRegisteredExtras( + lock, override, + func(extras ExtraPayloads[overrideCC, overrideRules]) error { // deliberately shadow `extras` + assertRulesCopiedFromChainConfig( + t, extras, "hello, world", + func(cc *overrideCC, x string) { cc.X = x }, + func(r *overrideRules) string { return r.X }, + ) + return nil + }, + ) + }) + require.NoError(t, err) }) t.Run("after_temp", testPrimaryExtras) } From 9f50a13be23fdc84d4d427eba22101d39bf22e39 Mon Sep 17 00:00:00 2001 From: Arran Schlosberg Date: Tue, 7 Oct 2025 16:03:20 +0100 Subject: [PATCH 2/6] refactor!: all temp-registration functions require a lock --- core/state/statedb.libevm.go | 8 ++++++-- core/state/statedb.libevm_test.go | 17 +++++++++++------ core/types/rlp_payload.libevm.go | 8 ++++++-- core/types/tempextras.libevm_test.go | 25 +++++++++++++++---------- core/vm/evm.libevm_test.go | 11 ++++++++--- core/vm/hooks.libevm.go | 8 ++++++-- libevm/register/register.go | 13 +++++++------ libevm/register/register_test.go | 18 ++++++++++++++---- params/config.libevm.go | 5 +---- 9 files changed, 74 insertions(+), 39 deletions(-) diff --git a/core/state/statedb.libevm.go b/core/state/statedb.libevm.go index 5b9d7d10420..327ae1dc5b9 100644 --- a/core/state/statedb.libevm.go +++ b/core/state/statedb.libevm.go @@ -21,6 +21,7 @@ import ( "github.com/ava-labs/libevm/common" "github.com/ava-labs/libevm/core/state/snapshot" + "github.com/ava-labs/libevm/libevm" "github.com/ava-labs/libevm/libevm/register" "github.com/ava-labs/libevm/libevm/stateconf" ) @@ -91,8 +92,11 @@ func RegisterExtras(s StateDBHooks) { // consumers that require access to extras. Said consumers SHOULD NOT, however // call this function directly. Use the libevm/temporary.WithRegisteredExtras() // function instead as it atomically overrides all possible packages. -func WithTempRegisteredExtras(s StateDBHooks, fn func()) { - registeredExtras.TempOverride(s, fn) +func WithTempRegisteredExtras(lock libevm.ExtrasLock, s StateDBHooks, fn func() error) error { + if err := lock.Verify(); err != nil { + return err + } + return registeredExtras.TempOverride(s, fn) } // TestOnlyClearRegisteredExtras clears the arguments previously passed to diff --git a/core/state/statedb.libevm_test.go b/core/state/statedb.libevm_test.go index 5cb3c8d5acb..4c3f17827a5 100644 --- a/core/state/statedb.libevm_test.go +++ b/core/state/statedb.libevm_test.go @@ -27,6 +27,7 @@ import ( "github.com/ava-labs/libevm/core/state/snapshot" "github.com/ava-labs/libevm/core/types" "github.com/ava-labs/libevm/ethdb" + "github.com/ava-labs/libevm/libevm" "github.com/ava-labs/libevm/libevm/stateconf" "github.com/ava-labs/libevm/trie" "github.com/ava-labs/libevm/trie/trienode" @@ -216,13 +217,17 @@ func TestTransformStateKey(t *testing.T) { assertCommittedEq(t, flippedKey, flippedVal, noTransform) t.Run("WithTempRegisteredExtras", func(t *testing.T) { - WithTempRegisteredExtras(noopHooks{}, func() { - // No-op hooks are equivalent to using the `noTransform` option. - // NOTE this is NOT the intended usage of [WithTempRegisteredExtras] - // and is simply an easy way to test the temporary registration. - assertEq(t, regularKey, regularVal) - assertEq(t, flippedKey, flippedVal) + err := libevm.WithTemporaryExtrasLock(func(lock libevm.ExtrasLock) error { + return WithTempRegisteredExtras(lock, noopHooks{}, func() error { + // No-op hooks are equivalent to using the `noTransform` option. + // NOTE this is NOT the intended usage of [WithTempRegisteredExtras] + // and is simply an easy way to test the temporary registration. + assertEq(t, regularKey, regularVal) + assertEq(t, flippedKey, flippedVal) + return nil + }) }) + require.NoError(t, err) }) updatedVal := common.Hash{'u', 'p', 'd', 'a', 't', 'e', 'd'} diff --git a/core/types/rlp_payload.libevm.go b/core/types/rlp_payload.libevm.go index 82f3da30a81..f72790ad357 100644 --- a/core/types/rlp_payload.libevm.go +++ b/core/types/rlp_payload.libevm.go @@ -21,6 +21,7 @@ import ( "io" "github.com/ava-labs/libevm/common" + "github.com/ava-labs/libevm/libevm" "github.com/ava-labs/libevm/libevm/pseudo" "github.com/ava-labs/libevm/libevm/register" "github.com/ava-labs/libevm/libevm/testonly" @@ -114,9 +115,12 @@ func WithTempRegisteredExtras[ H, B, SA any, HPtr HeaderHooksPointer[H], BPtr BlockBodyHooksPointer[B, BPtr], -](fn func(ExtraPayloads[HPtr, BPtr, SA])) { +](lock libevm.ExtrasLock, fn func(ExtraPayloads[HPtr, BPtr, SA]) error) error { + if err := lock.Verify(); err != nil { + return err + } payloads, ctors := payloadsAndConstructors[H, HPtr, B, BPtr, SA]() - registeredExtras.TempOverride(ctors, func() { fn(payloads) }) + return registeredExtras.TempOverride(ctors, func() error { return fn(payloads) }) } // A HeaderHooksPointer is a type constraint for an implementation of diff --git a/core/types/tempextras.libevm_test.go b/core/types/tempextras.libevm_test.go index eb5bfb4bdf6..b01b7802138 100644 --- a/core/types/tempextras.libevm_test.go +++ b/core/types/tempextras.libevm_test.go @@ -22,6 +22,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/ava-labs/libevm/libevm" "github.com/ava-labs/libevm/rlp" ) @@ -58,19 +59,23 @@ func TestTempRegisteredExtras(t *testing.T) { t.Run("before_temp", testPrimaryExtras) t.Run("WithTempRegisteredExtras", func(t *testing.T) { - WithTempRegisteredExtras(func(extras ExtraPayloads[*NOOPHeaderHooks, *tempBlockBodyHooks, bool]) { - const val = "Hello, world" - b := new(Block) - payload := &tempBlockBodyHooks{X: val} - extras.Block.Set(b, payload) + err := libevm.WithTemporaryExtrasLock(func(lock libevm.ExtrasLock) error { + return WithTempRegisteredExtras(lock, func(extras ExtraPayloads[*NOOPHeaderHooks, *tempBlockBodyHooks, bool]) error { + const val = "Hello, world" + b := new(Block) + payload := &tempBlockBodyHooks{X: val} + extras.Block.Set(b, payload) - got, err := rlp.EncodeToBytes(b) - require.NoErrorf(t, err, "rlp.EncodeToBytes(%T) with %T hooks", b, extras.Block.Get(b)) - want, err := rlp.EncodeToBytes([]string{val}) - require.NoErrorf(t, err, "rlp.EncodeToBytes(%T{%[1]v})", []string{val}) + got, err := rlp.EncodeToBytes(b) + require.NoErrorf(t, err, "rlp.EncodeToBytes(%T) with %T hooks", b, extras.Block.Get(b)) + want, err := rlp.EncodeToBytes([]string{val}) + require.NoErrorf(t, err, "rlp.EncodeToBytes(%T{%[1]v})", []string{val}) - assert.Equalf(t, want, got, "rlp.EncodeToBytes(%T) with %T hooks", b, payload) + assert.Equalf(t, want, got, "rlp.EncodeToBytes(%T) with %T hooks", b, payload) + return nil + }) }) + require.NoError(t, err) }) t.Run("after_temp", testPrimaryExtras) } diff --git a/core/vm/evm.libevm_test.go b/core/vm/evm.libevm_test.go index cc98d4bc1ac..c2e6708f725 100644 --- a/core/vm/evm.libevm_test.go +++ b/core/vm/evm.libevm_test.go @@ -22,6 +22,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/ava-labs/libevm/libevm" "github.com/ava-labs/libevm/params" ) @@ -72,10 +73,14 @@ func TestOverrideNewEVMArgs(t *testing.T) { assertChainID(t, chainID) t.Run("WithTempRegisteredHooks", func(t *testing.T) { - override := evmArgOverrider{newEVMchainID: 24680} - WithTempRegisteredHooks(&override, func() { - assertChainID(t, override.newEVMchainID) + err := libevm.WithTemporaryExtrasLock(func(lock libevm.ExtrasLock) error { + override := evmArgOverrider{newEVMchainID: 24680} + return WithTempRegisteredHooks(lock, &override, func() error { + assertChainID(t, override.newEVMchainID) + return nil + }) }) + require.NoError(t, err) t.Run("after", func(t *testing.T) { assertChainID(t, chainID) }) diff --git a/core/vm/hooks.libevm.go b/core/vm/hooks.libevm.go index 16e49f3bdcb..9c4feb1339b 100644 --- a/core/vm/hooks.libevm.go +++ b/core/vm/hooks.libevm.go @@ -17,6 +17,7 @@ package vm import ( + "github.com/ava-labs/libevm/libevm" "github.com/ava-labs/libevm/libevm/register" "github.com/ava-labs/libevm/params" ) @@ -36,8 +37,11 @@ func RegisterHooks(h Hooks) { // consumers that require access to extras. Said consumers SHOULD NOT, however // call this function directly. Use the libevm/temporary.WithRegisteredExtras() // function instead as it atomically overrides all possible packages. -func WithTempRegisteredHooks(h Hooks, fn func()) { - libevmHooks.TempOverride(h, fn) +func WithTempRegisteredHooks(lock libevm.ExtrasLock, h Hooks, fn func() error) error { + if err := lock.Verify(); err != nil { + return err + } + return libevmHooks.TempOverride(h, fn) } // TestOnlyClearRegisteredHooks clears the [Hooks] previously passed to diff --git a/libevm/register/register.go b/libevm/register/register.go index b8bb246e86f..4aedd370104 100644 --- a/libevm/register/register.go +++ b/libevm/register/register.go @@ -72,8 +72,8 @@ func (o *AtMostOnce[T]) TestOnlyClear() { // // It is valid to call this method with or without a prior call to // [AtMostOnce.Register]. -func (o *AtMostOnce[T]) TempOverride(with T, fn func()) { - o.temp(&with, fn) +func (o *AtMostOnce[T]) TempOverride(with T, fn func() error) error { + return o.temp(&with, fn) } // TempClear calls `fn`, clearing any registered `T`, but only for the life of @@ -81,13 +81,14 @@ func (o *AtMostOnce[T]) TempOverride(with T, fn func()) { // // It is valid to call this method with or without a prior call to // [AtMostOnce.Register]. -func (o *AtMostOnce[T]) TempClear(fn func()) { - o.temp(nil, fn) +func (o *AtMostOnce[T]) TempClear(fn func() error) error { + return o.temp(nil, fn) } -func (o *AtMostOnce[T]) temp(with *T, fn func()) { +func (o *AtMostOnce[T]) temp(with *T, fn func() error) error { old := o.v o.v = with - fn() + err := fn() o.v = old + return err } diff --git a/libevm/register/register_test.go b/libevm/register/register_test.go index fa8e1f70714..d06c6b1d06e 100644 --- a/libevm/register/register_test.go +++ b/libevm/register/register_test.go @@ -17,6 +17,7 @@ package register import ( + "errors" "testing" "github.com/stretchr/testify/assert" @@ -56,9 +57,10 @@ func TestAtMostOnce(t *testing.T) { t.Run("TempOverride", func(t *testing.T) { t.Run("during", func(t *testing.T) { - sut.TempOverride(val+1, func() { + require.NoError(t, sut.TempOverride(val+1, func() error { assertRegistered(t, val+1) - }) + return nil + })) }) t.Run("after", func(t *testing.T) { assertRegistered(t, val) @@ -67,12 +69,20 @@ func TestAtMostOnce(t *testing.T) { t.Run("TempClear", func(t *testing.T) { t.Run("during", func(t *testing.T) { - sut.TempClear(func() { + require.NoError(t, sut.TempClear(func() error { assert.False(t, sut.Registered(), "Registered()") - }) + return nil + })) }) t.Run("after", func(t *testing.T) { assertRegistered(t, val) }) }) + + t.Run("error_propagation", func(t *testing.T) { + errFoo := errors.New("foo") + fn := func() error { return errFoo } + assert.ErrorIs(t, sut.TempOverride(0, fn), errFoo, "TempOverride()") + assert.ErrorIs(t, sut.TempClear(fn), errFoo, "TempClear()") + }) } diff --git a/params/config.libevm.go b/params/config.libevm.go index 02bc062db28..0cbae661a21 100644 --- a/params/config.libevm.go +++ b/params/config.libevm.go @@ -114,11 +114,8 @@ func WithTempRegisteredExtras[C ChainConfigHooks, R RulesHooks]( if err := lock.Verify(); err != nil { return err } - payloads, ctors := payloadsAndConstructors(e) - var err error - registeredExtras.TempOverride(ctors, func() { err = fn(payloads) }) - return err + return registeredExtras.TempOverride(ctors, func() error { return fn(payloads) }) } // TestOnlyClearRegisteredExtras clears the [Extras] previously passed to From d6d593584e13ebd410d8bc7ba9a4b8fdb20d9c84 Mon Sep 17 00:00:00 2001 From: Arran Schlosberg Date: Tue, 7 Oct 2025 16:09:13 +0100 Subject: [PATCH 3/6] refactor: test of `libevm.ExtrasLock` --- libevm/extraslock_test.go | 38 +++++++++++++++++++++++--------------- 1 file changed, 23 insertions(+), 15 deletions(-) diff --git a/libevm/extraslock_test.go b/libevm/extraslock_test.go index c5ae998f233..f673c9aa915 100644 --- a/libevm/extraslock_test.go +++ b/libevm/extraslock_test.go @@ -20,6 +20,7 @@ import ( "testing" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" // Testing from outside the package to guarantee usage of the public API only. . "github.com/ava-labs/libevm/libevm" @@ -27,21 +28,28 @@ import ( func TestExtrasLock(t *testing.T) { var zero ExtrasLock - assert.Panics(t, func() { zero.Verify() }, "Verify() method of zero-value ExtrasLock{}") - - assert.NoError(t, - WithTemporaryExtrasLock((ExtrasLock).Verify), - "WithTemporaryExtrasLock((ExtrasLock).Verify)", - ) - - var persisted ExtrasLock - WithTemporaryExtrasLock(func(l ExtrasLock) error { - persisted = l - return nil + assert.Panics(t, func() { _ = zero.Verify() }, "Verify() method of zero-value ExtrasLock{}") + + testIntegration := func(t *testing.T) { + t.Helper() + require.NoError(t, + WithTemporaryExtrasLock((ExtrasLock).Verify), + "WithTemporaryExtrasLock((ExtrasLock).Verify)", + ) + } + t.Run("initial_usage", testIntegration) + + t.Run("lock_expiration", func(t *testing.T) { + var persisted ExtrasLock + require.NoError(t, WithTemporaryExtrasLock(func(l ExtrasLock) error { + persisted = l + return l.Verify() + })) + assert.ErrorIs( + t, persisted.Verify(), ErrExpiredExtrasLock, + "Verify() of persisted ExtrasLock", + ) }) - assert.ErrorIs( - t, persisted.Verify(), ErrExpiredExtrasLock, - "Verify() of persisted ExtrasLock", - ) + t.Run("repeat_usage", testIntegration) } From 264c00fdf8191afa982465a690480c274afa4634 Mon Sep 17 00:00:00 2001 From: Arran Schlosberg Date: Tue, 7 Oct 2025 16:26:10 +0100 Subject: [PATCH 4/6] chore: put the linter in its place --- libevm/extraslock_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libevm/extraslock_test.go b/libevm/extraslock_test.go index f673c9aa915..7ce4befa0d8 100644 --- a/libevm/extraslock_test.go +++ b/libevm/extraslock_test.go @@ -45,7 +45,7 @@ func TestExtrasLock(t *testing.T) { persisted = l return l.Verify() })) - assert.ErrorIs( + assert.ErrorIs( //nolint:testifylint // Blindly using require is an anti-pattern!!! t, persisted.Verify(), ErrExpiredExtrasLock, "Verify() of persisted ExtrasLock", ) From f747f0ae0d2bc27cdadf84225851041d83f0f9da Mon Sep 17 00:00:00 2001 From: Arran Schlosberg Date: Tue, 7 Oct 2025 16:34:24 +0100 Subject: [PATCH 5/6] chore: FML --- libevm/extraslock_test.go | 5 +---- libevm/register/register_test.go | 2 +- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/libevm/extraslock_test.go b/libevm/extraslock_test.go index 7ce4befa0d8..bdd0adeff97 100644 --- a/libevm/extraslock_test.go +++ b/libevm/extraslock_test.go @@ -45,10 +45,7 @@ func TestExtrasLock(t *testing.T) { persisted = l return l.Verify() })) - assert.ErrorIs( //nolint:testifylint // Blindly using require is an anti-pattern!!! - t, persisted.Verify(), ErrExpiredExtrasLock, - "Verify() of persisted ExtrasLock", - ) + assert.ErrorIs(t, persisted.Verify(), ErrExpiredExtrasLock, "Verify() of persisted ExtrasLock") }) t.Run("repeat_usage", testIntegration) diff --git a/libevm/register/register_test.go b/libevm/register/register_test.go index d06c6b1d06e..615a01ea39b 100644 --- a/libevm/register/register_test.go +++ b/libevm/register/register_test.go @@ -82,7 +82,7 @@ func TestAtMostOnce(t *testing.T) { t.Run("error_propagation", func(t *testing.T) { errFoo := errors.New("foo") fn := func() error { return errFoo } - assert.ErrorIs(t, sut.TempOverride(0, fn), errFoo, "TempOverride()") + assert.ErrorIs(t, sut.TempOverride(0, fn), errFoo, "TempOverride()") //nolint:testifylint // Blindly using require is an anti-pattern!!! assert.ErrorIs(t, sut.TempClear(fn), errFoo, "TempClear()") }) } From 3ee649c91723fa0e49f3cae4cd72ffb29186930f Mon Sep 17 00:00:00 2001 From: Arran Schlosberg Date: Tue, 7 Oct 2025 16:35:55 +0100 Subject: [PATCH 6/6] refactor: readability --- libevm/extraslock.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/libevm/extraslock.go b/libevm/extraslock.go index 32db70bdb1b..40326241268 100644 --- a/libevm/extraslock.go +++ b/libevm/extraslock.go @@ -27,6 +27,12 @@ var ( extrasHandle atomic.Uint64 ) +// An ExtrasLock is a handle that proves a current call to +// [WithTemporaryExtrasLock]. +type ExtrasLock struct { + handle *uint64 +} + // WithTemporaryExtrasLock takes a global lock and calls `fn` with a handle that // can be used to prove that the lock is held. All package-specific temporary // overrides require this proof. @@ -48,12 +54,6 @@ func WithTemporaryExtrasLock(fn func(lock ExtrasLock) error) error { // persisted beyond the call to [WithTemporaryExtrasLock] that created it. var ErrExpiredExtrasLock = errors.New("libevm.ExtrasLock expired") -// An ExtrasLock is a handle that proves a current call to -// [WithTemporaryExtrasLock]. -type ExtrasLock struct { - handle *uint64 -} - // Verify verifies that the lock is valid. func (l ExtrasLock) Verify() error { if *l.handle != extrasHandle.Load() {