mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-06-29 01:37:37 +00:00
feat: temporary extras (#234)
## Why this should be merged Registration of extras requires runtime enforcement of payload types. In production, only a single set of types can be registered and any attempts at re-registration will panic (by design). Although this makes production usage safe, it doesn't allow downstream consumers (e.g. data indexers) to use extras from different chains (e.g. `coreth` _and_ `subnet-evm`) at the same time. ## How this works 1. The `libevm/register.AtMostOnce` type can now be overridden temporarily. 2. `params`, `core/types`, `core/vm`, and `state` packages introduce `WithTempRegisteredExtras()` functions. 3. `libevm/temporary.WithRegisteredExtras()` provides "atomic" override of all extras. In all cases, the scope of the override is limited to the life of a single function call. ## How this was tested Relative to numbered list above: 1. Unit test of new and existing functionality. 2. Integration tests of both packages, demonstrating both payload and behavioural override.
This commit is contained in:
parent
414b1f5dff
commit
35926db4d6
12 changed files with 481 additions and 34 deletions
|
|
@ -82,6 +82,19 @@ func RegisterExtras(s StateDBHooks) {
|
|||
registeredExtras.MustRegister(s)
|
||||
}
|
||||
|
||||
// WithTempRegisteredExtras temporarily registers `s` as if calling
|
||||
// [RegisterExtras] the same type parameter. After `fn` returns, the
|
||||
// registration is returned to its former state, be that none or the types
|
||||
// originally passed to [RegisterExtras].
|
||||
//
|
||||
// This MUST NOT be used on a live chain. It is solely intended for off-chain
|
||||
// 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)
|
||||
}
|
||||
|
||||
// TestOnlyClearRegisteredExtras clears the arguments previously passed to
|
||||
// [RegisterExtras]. It panics if called from a non-testing call stack.
|
||||
//
|
||||
|
|
|
|||
|
|
@ -150,6 +150,12 @@ func (highByteFlipper) TransformStateKey(_ common.Address, key common.Hash) comm
|
|||
return flipHighByte(key)
|
||||
}
|
||||
|
||||
type noopHooks struct{}
|
||||
|
||||
func (noopHooks) TransformStateKey(_ common.Address, key common.Hash) common.Hash {
|
||||
return key
|
||||
}
|
||||
|
||||
func TestTransformStateKey(t *testing.T) {
|
||||
rawdb := rawdb.NewMemoryDatabase()
|
||||
trie := triedb.NewDatabase(rawdb, nil)
|
||||
|
|
@ -209,6 +215,16 @@ func TestTransformStateKey(t *testing.T) {
|
|||
assertCommittedEq(t, flippedKey, regularVal)
|
||||
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)
|
||||
})
|
||||
})
|
||||
|
||||
updatedVal := common.Hash{'u', 'p', 'd', 'a', 't', 'e', 'd'}
|
||||
sdb.SetState(addr, regularKey, updatedVal)
|
||||
assertEq(t, regularKey, updatedVal)
|
||||
|
|
|
|||
|
|
@ -130,7 +130,7 @@ type BlockBodyHooks interface {
|
|||
// to no type having been registered.
|
||||
type NOOPBlockBodyHooks struct{}
|
||||
|
||||
var _ BlockBodyPayload[*NOOPBlockBodyHooks] = NOOPBlockBodyHooks{}
|
||||
var _ BlockBodyPayload[*NOOPBlockBodyHooks] = (*NOOPBlockBodyHooks)(nil)
|
||||
|
||||
func (NOOPBlockBodyHooks) Copy() *NOOPBlockBodyHooks { return &NOOPBlockBodyHooks{} }
|
||||
|
||||
|
|
|
|||
|
|
@ -44,17 +44,27 @@ import (
|
|||
// [Header] or [Block] / [Body] is a non-nil `HPtr` or `BPtr` respectively. The
|
||||
// latter guarantee ensures that hooks won't be called on nil-pointer receivers.
|
||||
func RegisterExtras[
|
||||
H any, HPtr interface {
|
||||
HeaderHooks
|
||||
*H
|
||||
},
|
||||
B any, BPtr interface {
|
||||
BlockBodyPayload[BPtr]
|
||||
*B
|
||||
},
|
||||
H any, HPtr HeaderHooksPointer[H],
|
||||
B any, BPtr BlockBodyHooksPointer[B, BPtr],
|
||||
SA any,
|
||||
]() ExtraPayloads[HPtr, BPtr, SA] {
|
||||
extra := ExtraPayloads[HPtr, BPtr, SA]{
|
||||
payloads, ctors := payloadsAndConstructors[H, HPtr, B, BPtr, SA]()
|
||||
registeredExtras.MustRegister(ctors)
|
||||
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 payloads
|
||||
}
|
||||
|
||||
func payloadsAndConstructors[
|
||||
H any, HPtr HeaderHooksPointer[H],
|
||||
B any, BPtr BlockBodyHooksPointer[B, BPtr],
|
||||
SA any,
|
||||
]() (ExtraPayloads[HPtr, BPtr, SA], *extraConstructors) {
|
||||
payloads := ExtraPayloads[HPtr, BPtr, SA]{
|
||||
Header: pseudo.NewAccessor[*Header, HPtr](
|
||||
(*Header).extraPayload,
|
||||
func(h *Header, t *pseudo.Type) { h.extra = t },
|
||||
|
|
@ -72,7 +82,7 @@ func RegisterExtras[
|
|||
func(a StateOrSlimAccount, t *pseudo.Type) { a.extra().t = t },
|
||||
),
|
||||
}
|
||||
registeredExtras.MustRegister(&extraConstructors{
|
||||
ctors := &extraConstructors{
|
||||
stateAccountType: func() string {
|
||||
var x SA
|
||||
return fmt.Sprintf("%T", x)
|
||||
|
|
@ -84,23 +94,51 @@ func RegisterExtras[
|
|||
newHeader: pseudo.NewConstructor[H]().NewPointer, // i.e. non-nil HPtr
|
||||
newBlockOrBody: pseudo.NewConstructor[B]().NewPointer, // i.e. non-nil BPtr
|
||||
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
|
||||
hooks: payloads,
|
||||
}
|
||||
return payloads, ctors
|
||||
}
|
||||
|
||||
// WithTempRegisteredExtras temporarily registers `HPtr`, `BPtr`, and `SA` as if
|
||||
// calling [RegisterExtras] the same type parameters. The [ExtraPayloads] are
|
||||
// passed to `fn` instead of being returned; the argument MUST NOT be persisted
|
||||
// beyond the life of `fn`. After `fn` returns, the registration is returned to
|
||||
// its former state, be that none or the types originally passed to
|
||||
// [RegisterExtras].
|
||||
//
|
||||
// This MUST NOT be used on a live chain. It is solely intended for off-chain
|
||||
// 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[
|
||||
H, B, SA any,
|
||||
HPtr HeaderHooksPointer[H],
|
||||
BPtr BlockBodyHooksPointer[B, BPtr],
|
||||
](fn func(ExtraPayloads[HPtr, BPtr, SA])) {
|
||||
payloads, ctors := payloadsAndConstructors[H, HPtr, B, BPtr, SA]()
|
||||
registeredExtras.TempOverride(ctors, func() { fn(payloads) })
|
||||
}
|
||||
|
||||
// A HeaderHooksPointer is a type constraint for an implementation of
|
||||
// [HeaderHooks] with a pointer receiver.
|
||||
type HeaderHooksPointer[H any] interface {
|
||||
HeaderHooks
|
||||
*H
|
||||
}
|
||||
|
||||
// A BlockBodyHooksPointer is a type constraint for an implementation of
|
||||
// [BlockBodyPayload] with a pointer receiver.
|
||||
type BlockBodyHooksPointer[B any, Self any] interface {
|
||||
BlockBodyPayload[Self]
|
||||
*B
|
||||
}
|
||||
|
||||
// A BlockBodyPayload is an implementation of [BlockBodyHooks] that is also able
|
||||
// to clone itself. Both [Block.Body] and [Block.WithBody] require this
|
||||
// functionality to copy the payload between the types.
|
||||
type BlockBodyPayload[BPtr any] interface {
|
||||
type BlockBodyPayload[Self any] interface {
|
||||
BlockBodyHooks
|
||||
Copy() BPtr
|
||||
Copy() Self
|
||||
}
|
||||
|
||||
// TestOnlyClearRegisteredExtras clears the [Extras] previously passed to
|
||||
|
|
|
|||
76
core/types/tempextras.libevm_test.go
Normal file
76
core/types/tempextras.libevm_test.go
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
// 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
|
||||
// <http://www.gnu.org/licenses/>.
|
||||
|
||||
package types
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/ava-labs/libevm/rlp"
|
||||
)
|
||||
|
||||
type tempBlockBodyHooks struct {
|
||||
X string
|
||||
NOOPBlockBodyHooks
|
||||
}
|
||||
|
||||
func (b *tempBlockBodyHooks) Copy() *tempBlockBodyHooks {
|
||||
return &tempBlockBodyHooks{X: b.X}
|
||||
}
|
||||
|
||||
func (b *tempBlockBodyHooks) BlockRLPFieldsForEncoding(*BlockRLPProxy) *rlp.Fields {
|
||||
return &rlp.Fields{
|
||||
Required: []any{b.X},
|
||||
}
|
||||
}
|
||||
|
||||
func TestTempRegisteredExtras(t *testing.T) {
|
||||
TestOnlyClearRegisteredExtras()
|
||||
t.Cleanup(TestOnlyClearRegisteredExtras)
|
||||
|
||||
rlpWithoutHooks, err := rlp.EncodeToBytes(&Block{})
|
||||
require.NoErrorf(t, err, "rlp.EncodeToBytes(%T) without hooks", &Block{})
|
||||
|
||||
extras := RegisterExtras[NOOPHeaderHooks, *NOOPHeaderHooks, NOOPBlockBodyHooks, *NOOPBlockBodyHooks, bool]()
|
||||
testPrimaryExtras := func(t *testing.T) {
|
||||
t.Helper()
|
||||
b := new(Block)
|
||||
got, err := rlp.EncodeToBytes(b)
|
||||
require.NoErrorf(t, err, "rlp.EncodeToBytes(%T) with %T hooks", b, extras.Block.Get(b))
|
||||
assert.Equalf(t, rlpWithoutHooks, got, "rlp.EncodeToBytes(%T) with noop hooks; expect same as without hooks", b)
|
||||
}
|
||||
|
||||
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)
|
||||
|
||||
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)
|
||||
})
|
||||
})
|
||||
t.Run("after_temp", testPrimaryExtras)
|
||||
}
|
||||
|
|
@ -63,9 +63,23 @@ func TestOverrideNewEVMArgs(t *testing.T) {
|
|||
hooks := evmArgOverrider{newEVMchainID: chainID}
|
||||
hooks.register(t)
|
||||
|
||||
evm := NewEVM(BlockContext{}, TxContext{}, nil, nil, Config{})
|
||||
got := evm.ChainConfig().ChainID
|
||||
require.Equalf(t, big.NewInt(chainID), got, "%T.ChainConfig().ChainID set by NewEVM() hook", evm)
|
||||
assertChainID := func(t *testing.T, want int64) {
|
||||
t.Helper()
|
||||
evm := NewEVM(BlockContext{}, TxContext{}, nil, nil, Config{})
|
||||
got := evm.ChainConfig().ChainID
|
||||
require.Equalf(t, big.NewInt(want), got, "%T.ChainConfig().ChainID set by NewEVM() hook", evm)
|
||||
}
|
||||
assertChainID(t, chainID)
|
||||
|
||||
t.Run("WithTempRegisteredHooks", func(t *testing.T) {
|
||||
override := evmArgOverrider{newEVMchainID: 24680}
|
||||
WithTempRegisteredHooks(&override, func() {
|
||||
assertChainID(t, override.newEVMchainID)
|
||||
})
|
||||
t.Run("after", func(t *testing.T) {
|
||||
assertChainID(t, chainID)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func TestOverrideEVMResetArgs(t *testing.T) {
|
||||
|
|
|
|||
|
|
@ -27,6 +27,19 @@ func RegisterHooks(h Hooks) {
|
|||
libevmHooks.MustRegister(h)
|
||||
}
|
||||
|
||||
// WithTempRegisteredHooks temporarily registers `h` as if calling
|
||||
// [RegisterHooks] the same type parameter. After `fn` returns, the registration
|
||||
// is returned to its former state, be that none or the types originally passed
|
||||
// to [RegisterHooks].
|
||||
//
|
||||
// This MUST NOT be used on a live chain. It is solely intended for off-chain
|
||||
// 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)
|
||||
}
|
||||
|
||||
// TestOnlyClearRegisteredHooks clears the [Hooks] previously passed to
|
||||
// [RegisterHooks]. It panics if called from a non-testing call stack.
|
||||
func TestOnlyClearRegisteredHooks() {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -66,3 +66,28 @@ func (o *AtMostOnce[T]) TestOnlyClear() {
|
|||
o.v = nil
|
||||
})
|
||||
}
|
||||
|
||||
// TempOverride calls `fn`, overriding any registered `T`, but only for the life
|
||||
// of the call. It is not threadsafe.
|
||||
//
|
||||
// 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)
|
||||
}
|
||||
|
||||
// TempClear calls `fn`, clearing any registered `T`, but only for the life of
|
||||
// the call. It is not threadsafe.
|
||||
//
|
||||
// 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]) temp(with *T, fn func()) {
|
||||
old := o.v
|
||||
o.v = with
|
||||
fn()
|
||||
o.v = old
|
||||
}
|
||||
|
|
|
|||
78
libevm/register/register_test.go
Normal file
78
libevm/register/register_test.go
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
// 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
|
||||
// <http://www.gnu.org/licenses/>.
|
||||
|
||||
package register
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestAtMostOnce(t *testing.T) {
|
||||
var sut AtMostOnce[int]
|
||||
assertRegistered := func(t *testing.T, want int) {
|
||||
t.Helper()
|
||||
require.True(t, sut.Registered(), "Registered()")
|
||||
assert.Equal(t, want, sut.Get(), "Get()")
|
||||
}
|
||||
|
||||
const val int = 42
|
||||
require.NoError(t, sut.Register(val), "Register()")
|
||||
assertRegistered(t, val)
|
||||
|
||||
assert.PanicsWithValue(
|
||||
t, ErrReRegistration,
|
||||
func() { sut.MustRegister(0) },
|
||||
"MustRegister() after Register()",
|
||||
)
|
||||
|
||||
t.Run("TestOnlyClear", func(t *testing.T) {
|
||||
sut.TestOnlyClear()
|
||||
require.False(t, sut.Registered(), "Registered()")
|
||||
|
||||
t.Run("re-registration", func(t *testing.T) {
|
||||
sut.MustRegister(val)
|
||||
assertRegistered(t, val)
|
||||
})
|
||||
})
|
||||
if t.Failed() {
|
||||
return
|
||||
}
|
||||
|
||||
t.Run("TempOverride", func(t *testing.T) {
|
||||
t.Run("during", func(t *testing.T) {
|
||||
sut.TempOverride(val+1, func() {
|
||||
assertRegistered(t, val+1)
|
||||
})
|
||||
})
|
||||
t.Run("after", func(t *testing.T) {
|
||||
assertRegistered(t, val)
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("TempClear", func(t *testing.T) {
|
||||
t.Run("during", func(t *testing.T) {
|
||||
sut.TempClear(func() {
|
||||
assert.False(t, sut.Registered(), "Registered()")
|
||||
})
|
||||
})
|
||||
t.Run("after", func(t *testing.T) {
|
||||
assertRegistered(t, val)
|
||||
})
|
||||
})
|
||||
}
|
||||
65
libevm/temporary/temporary.go
Normal file
65
libevm/temporary/temporary.go
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
// 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
|
||||
// <http://www.gnu.org/licenses/>.
|
||||
|
||||
// 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)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
|
@ -72,14 +72,8 @@ func RegisterExtras[C ChainConfigHooks, R RulesHooks](e Extras[C, R]) ExtraPaylo
|
|||
mustBeStructOrPointerToOne[C]()
|
||||
mustBeStructOrPointerToOne[R]()
|
||||
|
||||
payloads := e.payloads()
|
||||
registeredExtras.MustRegister(&extraConstructors{
|
||||
newChainConfig: pseudo.NewConstructor[C]().Zero,
|
||||
newRules: pseudo.NewConstructor[R]().Zero,
|
||||
reuseJSONRoot: e.ReuseJSONRoot,
|
||||
newForRules: e.newForRules,
|
||||
payloads: payloads,
|
||||
})
|
||||
payloads, ctors := payloadsAndConstructors(e)
|
||||
registeredExtras.MustRegister(ctors)
|
||||
log.Info(
|
||||
"Registered params extras",
|
||||
"ChainConfig", log.TypeOf(pseudo.Zero[C]().Value.Get()),
|
||||
|
|
@ -89,6 +83,36 @@ func RegisterExtras[C ChainConfigHooks, R RulesHooks](e Extras[C, R]) ExtraPaylo
|
|||
return payloads
|
||||
}
|
||||
|
||||
func payloadsAndConstructors[C ChainConfigHooks, R RulesHooks](e Extras[C, R]) (ExtraPayloads[C, R], *extraConstructors) {
|
||||
payloads := e.payloads()
|
||||
return payloads, &extraConstructors{
|
||||
newChainConfig: pseudo.NewConstructor[C]().Zero,
|
||||
newRules: pseudo.NewConstructor[R]().Zero,
|
||||
reuseJSONRoot: e.ReuseJSONRoot,
|
||||
newForRules: e.newForRules,
|
||||
payloads: payloads,
|
||||
}
|
||||
}
|
||||
|
||||
// WithTempRegisteredExtras temporarily registers `HPtr`, `BPtr`, and `SA` as if
|
||||
// calling [RegisterExtras] the same type parameters. The [ExtraPayloads] are
|
||||
// passed to `fn` instead of being returned; the argument MUST NOT be persisted
|
||||
// beyond the life of `fn`. After `fn` returns, the registration is returned to
|
||||
// its former state, be that none or the types originally passed to
|
||||
// [RegisterExtras].
|
||||
//
|
||||
// This MUST NOT be used on a live chain. It is solely intended for off-chain
|
||||
// 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[C ChainConfigHooks, R RulesHooks](
|
||||
e Extras[C, R],
|
||||
fn func(ExtraPayloads[C, R]),
|
||||
) {
|
||||
payloads, ctors := payloadsAndConstructors(e)
|
||||
registeredExtras.TempOverride(ctors, func() { fn(payloads) })
|
||||
}
|
||||
|
||||
// TestOnlyClearRegisteredExtras clears the [Extras] previously passed to
|
||||
// [RegisterExtras]. It panics if called from a non-testing call stack.
|
||||
//
|
||||
|
|
|
|||
|
|
@ -277,3 +277,88 @@ func assertPanics(t *testing.T, fn func(), wantContains string) {
|
|||
}()
|
||||
fn()
|
||||
}
|
||||
|
||||
func TestTempRegisteredExtras(t *testing.T) {
|
||||
TestOnlyClearRegisteredExtras()
|
||||
t.Cleanup(TestOnlyClearRegisteredExtras)
|
||||
|
||||
type (
|
||||
primaryCC struct {
|
||||
X int
|
||||
NOOPHooks
|
||||
}
|
||||
primaryRules struct {
|
||||
X int
|
||||
NOOPHooks
|
||||
}
|
||||
|
||||
overrideCC struct {
|
||||
X string
|
||||
NOOPHooks
|
||||
}
|
||||
overrideRules struct {
|
||||
X string
|
||||
NOOPHooks
|
||||
}
|
||||
)
|
||||
|
||||
primary := Extras[primaryCC, primaryRules]{
|
||||
NewRules: func(_ *ChainConfig, _ *Rules, cc primaryCC, _ *big.Int, _ bool, _ uint64) primaryRules {
|
||||
return primaryRules{
|
||||
X: cc.X,
|
||||
}
|
||||
},
|
||||
}
|
||||
override := Extras[overrideCC, overrideRules]{
|
||||
NewRules: func(_ *ChainConfig, _ *Rules, cc overrideCC, _ *big.Int, _ bool, _ uint64) overrideRules {
|
||||
return overrideRules{
|
||||
X: cc.X,
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
extras := RegisterExtras(primary)
|
||||
testPrimaryExtras := func(t *testing.T) {
|
||||
t.Helper()
|
||||
assertRulesCopiedFromChainConfig(
|
||||
t, extras, 42,
|
||||
func(cc *primaryCC, x int) { cc.X = x },
|
||||
func(r *primaryRules) int { return r.X },
|
||||
)
|
||||
}
|
||||
|
||||
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 },
|
||||
)
|
||||
},
|
||||
)
|
||||
})
|
||||
t.Run("after_temp", testPrimaryExtras)
|
||||
}
|
||||
|
||||
func assertRulesCopiedFromChainConfig[C ChainConfigHooks, R RulesHooks, Payload any](
|
||||
t *testing.T,
|
||||
extras ExtraPayloads[C, R],
|
||||
val Payload,
|
||||
setX func(*C, Payload),
|
||||
getX func(*R) Payload,
|
||||
) {
|
||||
t.Helper()
|
||||
|
||||
cc := new(ChainConfig)
|
||||
var ccExtra C
|
||||
setX(&ccExtra, val)
|
||||
|
||||
extras.ChainConfig.Set(cc, ccExtra)
|
||||
rules := cc.Rules(nil, false, 0)
|
||||
rulesExtra := extras.Rules.Get(&rules)
|
||||
|
||||
assert.Equalf(t, val, getX(&rulesExtra), "%T.X copied from %T.X", rulesExtra, ccExtra)
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue