mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-07-18 19:00:46 +00:00
core: use an arena allocator in the STF
This commit is contained in:
parent
cee751a1ed
commit
3f5b5dc3a3
22 changed files with 1203 additions and 56 deletions
|
|
@ -141,6 +141,7 @@ var (
|
|||
utils.VMTraceJsonConfigFlag,
|
||||
utils.VMWitnessStatsFlag,
|
||||
utils.VMStatelessSelfValidationFlag,
|
||||
utils.VMArenaAllocFlag,
|
||||
utils.NetworkIdFlag,
|
||||
utils.EthStatsURLFlag,
|
||||
utils.GpoBlocksFlag,
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ import (
|
|||
"runtime/debug"
|
||||
|
||||
"github.com/ethereum/go-ethereum/core"
|
||||
"github.com/ethereum/go-ethereum/core/arena"
|
||||
"github.com/ethereum/go-ethereum/core/stateless"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/core/vm"
|
||||
|
|
@ -51,7 +52,9 @@ func main() {
|
|||
fmt.Fprintf(os.Stderr, "failed to get chain config: %v\n", err)
|
||||
os.Exit(13)
|
||||
}
|
||||
vmConfig := vm.Config{}
|
||||
vmConfig := vm.Config{
|
||||
Allocator: arena.NewBumpAllocator(make([]byte, 32<<20)), // 32 MiB arena
|
||||
}
|
||||
|
||||
crossStateRoot, crossReceiptRoot, err := core.ExecuteStateless(context.Background(), chainConfig, vmConfig, payload.Block, payload.Witness)
|
||||
if err != nil {
|
||||
|
|
|
|||
|
|
@ -621,6 +621,11 @@ var (
|
|||
Usage: "Generate execution witnesses and self-check against them (testing purpose)",
|
||||
Category: flags.VMCategory,
|
||||
}
|
||||
VMArenaAllocFlag = &cli.BoolFlag{
|
||||
Name: "vmarena",
|
||||
Usage: "Use bump/arena allocator for transient EVM allocations during block processing",
|
||||
Category: flags.VMCategory,
|
||||
}
|
||||
// API options.
|
||||
RPCGlobalGasCapFlag = &cli.Uint64Flag{
|
||||
Name: "rpc.gascap",
|
||||
|
|
@ -1902,6 +1907,9 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *ethconfig.Config) {
|
|||
if ctx.Bool(VMWitnessStatsFlag.Name) {
|
||||
cfg.StatelessSelfValidation = true
|
||||
}
|
||||
if ctx.IsSet(VMArenaAllocFlag.Name) {
|
||||
cfg.EnableArenaAlloc = ctx.Bool(VMArenaAllocFlag.Name)
|
||||
}
|
||||
|
||||
if ctx.IsSet(RPCGlobalGasCapFlag.Name) {
|
||||
cfg.RPCGasCap = ctx.Uint64(RPCGlobalGasCapFlag.Name)
|
||||
|
|
|
|||
56
core/arena/allocator.go
Normal file
56
core/arena/allocator.go
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
// Copyright 2026 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it 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 go-ethereum library is distributed in the hope that it 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 arena
|
||||
|
||||
import "unsafe"
|
||||
|
||||
// Allocator is the interface for arena-style allocators. RawAlloc returns
|
||||
// a pointer to a zeroed region of at least `size` bytes, aligned to `align`.
|
||||
// Reset releases all memory allocated since the last Reset (or since creation).
|
||||
type Allocator interface {
|
||||
RawAlloc(size, align uintptr) unsafe.Pointer
|
||||
Reset()
|
||||
}
|
||||
|
||||
// New allocates and zeros a single value of type T from the given allocator.
|
||||
// When the allocator is a *HeapAllocator, this uses the standard `new(T)` and
|
||||
// involves no unsafe operations.
|
||||
func New[T any](a Allocator) *T {
|
||||
if _, ok := a.(*HeapAllocator); ok {
|
||||
return new(T)
|
||||
}
|
||||
var zero T
|
||||
size := unsafe.Sizeof(zero)
|
||||
align := unsafe.Alignof(zero)
|
||||
ptr := a.RawAlloc(size, align)
|
||||
return (*T)(ptr)
|
||||
}
|
||||
|
||||
// MakeSlice allocates a slice of type []T with the given length and capacity
|
||||
// from the allocator. When the allocator is a *HeapAllocator, this uses the
|
||||
// standard `make([]T, length, capacity)` and involves no unsafe operations.
|
||||
func MakeSlice[T any](a Allocator, length, capacity int) []T {
|
||||
if _, ok := a.(*HeapAllocator); ok {
|
||||
return make([]T, length, capacity)
|
||||
}
|
||||
var zero T
|
||||
elemSize := unsafe.Sizeof(zero)
|
||||
elemAlign := unsafe.Alignof(zero)
|
||||
totalSize := elemSize * uintptr(capacity)
|
||||
ptr := a.RawAlloc(totalSize, elemAlign)
|
||||
return unsafe.Slice((*T)(ptr), capacity)[:length]
|
||||
}
|
||||
257
core/arena/allocator_test.go
Normal file
257
core/arena/allocator_test.go
Normal file
|
|
@ -0,0 +1,257 @@
|
|||
// Copyright 2026 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it 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 go-ethereum library is distributed in the hope that it 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 arena
|
||||
|
||||
import (
|
||||
"math/big"
|
||||
"testing"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// testStruct is a struct with mixed field types for alignment testing.
|
||||
type testStruct struct {
|
||||
A uint64
|
||||
B uint32
|
||||
C byte
|
||||
D *big.Int
|
||||
}
|
||||
|
||||
func TestBumpAllocatorAlignment(t *testing.T) {
|
||||
slab := make([]byte, 4096)
|
||||
alloc := NewBumpAllocator(slab)
|
||||
|
||||
// Allocate a byte, then a uint64 — the uint64 must be properly aligned.
|
||||
alloc.RawAlloc(1, 1) // 1 byte, align 1
|
||||
|
||||
var zero uint64
|
||||
ptr := alloc.RawAlloc(unsafe.Sizeof(zero), unsafe.Alignof(zero))
|
||||
addr := uintptr(ptr)
|
||||
if addr%unsafe.Alignof(zero) != 0 {
|
||||
t.Fatalf("uint64 pointer %x not aligned to %d", addr, unsafe.Alignof(zero))
|
||||
}
|
||||
}
|
||||
|
||||
func TestBumpAllocatorZeroing(t *testing.T) {
|
||||
slab := make([]byte, 4096)
|
||||
alloc := NewBumpAllocator(slab)
|
||||
|
||||
// Dirty the slab.
|
||||
for i := range slab {
|
||||
slab[i] = 0xFF
|
||||
}
|
||||
|
||||
// Allocate and verify zeroed.
|
||||
ptr := alloc.RawAlloc(64, 1)
|
||||
data := unsafe.Slice((*byte)(ptr), 64)
|
||||
for i, b := range data {
|
||||
if b != 0 {
|
||||
t.Fatalf("byte %d not zeroed: got %x", i, b)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestBumpAllocatorMultiSlab(t *testing.T) {
|
||||
slab := make([]byte, 32)
|
||||
alloc := NewBumpAllocator(slab)
|
||||
|
||||
// Allocation bigger than first slab triggers a new slab.
|
||||
alloc.RawAlloc(64, 1)
|
||||
if alloc.SlabCount() != 2 {
|
||||
t.Fatalf("expected 2 slabs, got %d", alloc.SlabCount())
|
||||
}
|
||||
}
|
||||
|
||||
func TestBumpAllocatorTotalCap(t *testing.T) {
|
||||
slab := make([]byte, 32)
|
||||
alloc := NewBumpAllocator(slab)
|
||||
alloc.maxTotal = 128 // low cap for testing
|
||||
|
||||
defer func() {
|
||||
if r := recover(); r == nil {
|
||||
t.Fatal("expected panic on total cap exceeded, got nil")
|
||||
}
|
||||
}()
|
||||
// First extra slab (32 bytes) → total 64, ok.
|
||||
alloc.RawAlloc(64, 1)
|
||||
// Second extra slab → total would exceed 128.
|
||||
alloc.RawAlloc(128, 1)
|
||||
}
|
||||
|
||||
func TestBumpAllocatorReset(t *testing.T) {
|
||||
slab := make([]byte, 256)
|
||||
alloc := NewBumpAllocator(slab)
|
||||
|
||||
alloc.RawAlloc(128, 1)
|
||||
if alloc.Used() == 0 {
|
||||
t.Fatal("expected non-zero Used after alloc")
|
||||
}
|
||||
|
||||
alloc.Reset()
|
||||
if alloc.Used() != 0 {
|
||||
t.Fatalf("expected 0 Used after Reset, got %d", alloc.Used())
|
||||
}
|
||||
if alloc.Remaining() != 256 {
|
||||
t.Fatalf("expected 256 Remaining after Reset, got %d", alloc.Remaining())
|
||||
}
|
||||
|
||||
// Should be able to allocate again after Reset.
|
||||
alloc.RawAlloc(128, 1)
|
||||
}
|
||||
|
||||
func TestNewGenericHeap(t *testing.T) {
|
||||
alloc := &HeapAllocator{}
|
||||
|
||||
v := New[uint64](alloc)
|
||||
if *v != 0 {
|
||||
t.Fatalf("expected zero value, got %d", *v)
|
||||
}
|
||||
*v = 42
|
||||
if *v != 42 {
|
||||
t.Fatal("heap-allocated uint64 not writable")
|
||||
}
|
||||
|
||||
s := New[testStruct](alloc)
|
||||
if s.A != 0 || s.B != 0 || s.C != 0 || s.D != nil {
|
||||
t.Fatal("expected zero struct")
|
||||
}
|
||||
s.A = 1
|
||||
s.D = big.NewInt(99)
|
||||
}
|
||||
|
||||
func TestNewGenericBump(t *testing.T) {
|
||||
slab := make([]byte, 4096)
|
||||
alloc := NewBumpAllocator(slab)
|
||||
|
||||
v := New[uint64](alloc)
|
||||
if *v != 0 {
|
||||
t.Fatalf("expected zero value, got %d", *v)
|
||||
}
|
||||
*v = 42
|
||||
if *v != 42 {
|
||||
t.Fatal("bump-allocated uint64 not writable")
|
||||
}
|
||||
|
||||
s := New[testStruct](alloc)
|
||||
if s.A != 0 || s.B != 0 || s.C != 0 || s.D != nil {
|
||||
t.Fatal("expected zero struct")
|
||||
}
|
||||
s.A = 1
|
||||
|
||||
// Verify alignment.
|
||||
addr := uintptr(unsafe.Pointer(s))
|
||||
if addr%unsafe.Alignof(testStruct{}) != 0 {
|
||||
t.Fatalf("struct pointer %x not aligned to %d", addr, unsafe.Alignof(testStruct{}))
|
||||
}
|
||||
}
|
||||
|
||||
func TestMakeSliceHeap(t *testing.T) {
|
||||
alloc := &HeapAllocator{}
|
||||
|
||||
s := MakeSlice[uint64](alloc, 3, 8)
|
||||
if len(s) != 3 || cap(s) != 8 {
|
||||
t.Fatalf("unexpected len/cap: %d/%d", len(s), cap(s))
|
||||
}
|
||||
s[0] = 10
|
||||
s[1] = 20
|
||||
s[2] = 30
|
||||
if s[0] != 10 || s[1] != 20 || s[2] != 30 {
|
||||
t.Fatal("heap slice not writable")
|
||||
}
|
||||
}
|
||||
|
||||
func TestMakeSliceBump(t *testing.T) {
|
||||
slab := make([]byte, 4096)
|
||||
alloc := NewBumpAllocator(slab)
|
||||
|
||||
s := MakeSlice[uint64](alloc, 3, 8)
|
||||
if len(s) != 3 || cap(s) != 8 {
|
||||
t.Fatalf("unexpected len/cap: %d/%d", len(s), cap(s))
|
||||
}
|
||||
for i := range s {
|
||||
if s[i] != 0 {
|
||||
t.Fatalf("element %d not zeroed: %d", i, s[i])
|
||||
}
|
||||
}
|
||||
s[0] = 10
|
||||
s[1] = 20
|
||||
s[2] = 30
|
||||
if s[0] != 10 || s[1] != 20 || s[2] != 30 {
|
||||
t.Fatal("bump slice not writable")
|
||||
}
|
||||
}
|
||||
|
||||
func TestHeapAllocatorReset(t *testing.T) {
|
||||
alloc := &HeapAllocator{}
|
||||
|
||||
// RawAlloc pins data; Reset clears pins.
|
||||
alloc.RawAlloc(32, 1)
|
||||
alloc.RawAlloc(64, 1)
|
||||
if len(alloc.pins) != 2 {
|
||||
t.Fatalf("expected 2 pins, got %d", len(alloc.pins))
|
||||
}
|
||||
alloc.Reset()
|
||||
if len(alloc.pins) != 0 {
|
||||
t.Fatalf("expected 0 pins after Reset, got %d", len(alloc.pins))
|
||||
}
|
||||
}
|
||||
|
||||
func TestDefaultHeap(t *testing.T) {
|
||||
// DefaultHeap should be usable as an Allocator.
|
||||
var a Allocator = DefaultHeap
|
||||
v := New[int](a)
|
||||
if *v != 0 {
|
||||
t.Fatal("expected zero from DefaultHeap")
|
||||
}
|
||||
}
|
||||
|
||||
func TestBumpAllocatorNewBigInt(t *testing.T) {
|
||||
slab := make([]byte, 4096)
|
||||
alloc := NewBumpAllocator(slab)
|
||||
|
||||
b := New[big.Int](alloc)
|
||||
if b.Sign() != 0 {
|
||||
t.Fatal("expected zero big.Int")
|
||||
}
|
||||
b.SetInt64(123456789)
|
||||
if b.Int64() != 123456789 {
|
||||
t.Fatalf("unexpected value: %d", b.Int64())
|
||||
}
|
||||
}
|
||||
|
||||
func TestBumpResetAndReuse(t *testing.T) {
|
||||
slab := make([]byte, 256)
|
||||
alloc := NewBumpAllocator(slab)
|
||||
|
||||
// Fill up most of the slab.
|
||||
for i := 0; i < 10; i++ {
|
||||
New[uint64](alloc)
|
||||
}
|
||||
used := alloc.Used()
|
||||
if used == 0 {
|
||||
t.Fatal("expected non-zero usage")
|
||||
}
|
||||
|
||||
alloc.Reset()
|
||||
|
||||
// After reset, should be able to allocate again from the start.
|
||||
for i := 0; i < 10; i++ {
|
||||
v := New[uint64](alloc)
|
||||
if *v != 0 {
|
||||
t.Fatalf("iteration %d: expected zero after reset, got %d", i, *v)
|
||||
}
|
||||
}
|
||||
}
|
||||
141
core/arena/bump.go
Normal file
141
core/arena/bump.go
Normal file
|
|
@ -0,0 +1,141 @@
|
|||
// Copyright 2026 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it 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 go-ethereum library is distributed in the hope that it 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 arena
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultSlabSize = 8 << 20 // 8 MiB per slab
|
||||
maxTotalSize = 1 << 30 // 1 GiB total cap across all slabs
|
||||
)
|
||||
|
||||
// BumpAllocator is a multi-slab bump/arena allocator. It sub-allocates from
|
||||
// pre-allocated byte slabs, growing by adding new slabs when the current one
|
||||
// is exhausted. It is not thread-safe. All allocated memory is freed at once
|
||||
// via Reset.
|
||||
//
|
||||
// Zeroing is performed lazily at allocation time (not on Reset), using
|
||||
// clear() which compiles to an optimized memclr intrinsic.
|
||||
type BumpAllocator struct {
|
||||
slabs [][]byte // all slabs (index 0 = first allocated)
|
||||
current int // index of the active slab in slabs
|
||||
offset uintptr // offset within the current slab
|
||||
slabSize int // size of each new slab
|
||||
total uintptr // total bytes allocated across all slabs
|
||||
maxTotal uintptr // maximum total bytes (DoS protection)
|
||||
peak uintptr // high-water mark of Used() across resets
|
||||
}
|
||||
|
||||
// NewBumpAllocator creates a BumpAllocator with a single initial slab.
|
||||
// The slab size and total cap use defaults (8 MiB per slab, 1 GiB cap).
|
||||
func NewBumpAllocator(slab []byte) *BumpAllocator {
|
||||
return &BumpAllocator{
|
||||
slabs: [][]byte{slab},
|
||||
slabSize: len(slab),
|
||||
total: uintptr(len(slab)),
|
||||
maxTotal: maxTotalSize,
|
||||
}
|
||||
}
|
||||
|
||||
// RawAlloc returns a pointer to a zeroed region of at least `size` bytes,
|
||||
// aligned to `align`, from the current slab. If the current slab is
|
||||
// exhausted, a new slab is allocated. Panics if the total cap is exceeded.
|
||||
func (b *BumpAllocator) RawAlloc(size, align uintptr) unsafe.Pointer {
|
||||
slab := b.slabs[b.current]
|
||||
|
||||
// Align the current offset up to the required alignment.
|
||||
aligned := (b.offset + align - 1) &^ (align - 1)
|
||||
end := aligned + size
|
||||
|
||||
if end > uintptr(len(slab)) {
|
||||
// Current slab exhausted — try the next retained slab or allocate a new one.
|
||||
b.current++
|
||||
if b.current < len(b.slabs) {
|
||||
// Reuse a previously allocated slab.
|
||||
slab = b.slabs[b.current]
|
||||
} else {
|
||||
// Allocate a new slab, at least large enough for this request.
|
||||
newSize := b.slabSize
|
||||
if int(size) > newSize {
|
||||
newSize = int(size + align) // oversized allocation
|
||||
}
|
||||
if b.total+uintptr(newSize) > b.maxTotal {
|
||||
panic(fmt.Sprintf("arena: total allocation exceeds %d byte cap", b.maxTotal))
|
||||
}
|
||||
slab = make([]byte, newSize)
|
||||
b.slabs = append(b.slabs, slab)
|
||||
b.total += uintptr(newSize)
|
||||
}
|
||||
b.offset = 0
|
||||
aligned = 0 // offset 0 is always aligned
|
||||
end = size
|
||||
}
|
||||
|
||||
// Zero the region using clear() which compiles to optimized memclr.
|
||||
clear(slab[aligned:end])
|
||||
|
||||
b.offset = end
|
||||
return unsafe.Pointer(&slab[aligned])
|
||||
}
|
||||
|
||||
// Reset rewinds the allocator to the first slab. Retained slabs are kept
|
||||
// for reuse. Zeroing is deferred to the next RawAlloc call, so Reset is O(1).
|
||||
func (b *BumpAllocator) Reset() {
|
||||
used := b.Used()
|
||||
if used > b.peak {
|
||||
b.peak = used
|
||||
}
|
||||
b.current = 0
|
||||
b.offset = 0
|
||||
}
|
||||
|
||||
// Used returns the number of bytes currently allocated (across all active slabs).
|
||||
func (b *BumpAllocator) Used() uintptr {
|
||||
var total uintptr
|
||||
for i := 0; i < b.current; i++ {
|
||||
total += uintptr(len(b.slabs[i]))
|
||||
}
|
||||
total += b.offset
|
||||
return total
|
||||
}
|
||||
|
||||
// Remaining returns the number of bytes left in the current slab.
|
||||
func (b *BumpAllocator) Remaining() uintptr {
|
||||
return uintptr(len(b.slabs[b.current])) - b.offset
|
||||
}
|
||||
|
||||
// SlabCount returns the number of slabs allocated.
|
||||
func (b *BumpAllocator) SlabCount() int {
|
||||
return len(b.slabs)
|
||||
}
|
||||
|
||||
// TotalCapacity returns the total bytes across all slabs.
|
||||
func (b *BumpAllocator) TotalCapacity() uintptr {
|
||||
return b.total
|
||||
}
|
||||
|
||||
// Peak returns the high-water mark of Used() across all resets.
|
||||
func (b *BumpAllocator) Peak() uintptr {
|
||||
// Check current usage too (might not have been Reset yet).
|
||||
if used := b.Used(); used > b.peak {
|
||||
return used
|
||||
}
|
||||
return b.peak
|
||||
}
|
||||
45
core/arena/heap.go
Normal file
45
core/arena/heap.go
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
// Copyright 2026 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it 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 go-ethereum library is distributed in the hope that it 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 arena
|
||||
|
||||
import "unsafe"
|
||||
|
||||
// DefaultHeap is the package-level HeapAllocator used as the nil-allocator default.
|
||||
var DefaultHeap = &HeapAllocator{}
|
||||
|
||||
// HeapAllocator is a dummy allocator that delegates to Go's built-in heap.
|
||||
// The generic helpers (New[T], MakeSlice[T]) detect it via type assertion and
|
||||
// use new(T)/make([]T) directly, avoiding any unsafe operations. RawAlloc is
|
||||
// provided as a fallback for callers that go through the Allocator interface
|
||||
// without the generic helpers.
|
||||
type HeapAllocator struct {
|
||||
pins []any // GC roots for objects allocated via RawAlloc
|
||||
}
|
||||
|
||||
// RawAlloc allocates size bytes on the Go heap and pins the backing array to
|
||||
// prevent GC collection. This is only used as a fallback; the generic helpers
|
||||
// bypass it entirely via the type assertion fast path.
|
||||
func (h *HeapAllocator) RawAlloc(size, align uintptr) unsafe.Pointer {
|
||||
buf := make([]byte, size)
|
||||
h.pins = append(h.pins, buf)
|
||||
return unsafe.Pointer(unsafe.SliceData(buf))
|
||||
}
|
||||
|
||||
// Reset clears the pins slice, allowing GC to collect all RawAlloc'd memory.
|
||||
func (h *HeapAllocator) Reset() {
|
||||
h.pins = h.pins[:0]
|
||||
}
|
||||
288
core/arena_integration_test.go
Normal file
288
core/arena_integration_test.go
Normal file
|
|
@ -0,0 +1,288 @@
|
|||
// Copyright 2026 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it 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 go-ethereum library is distributed in the hope that it 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 core
|
||||
|
||||
import (
|
||||
"context"
|
||||
"math/big"
|
||||
"testing"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/consensus/beacon"
|
||||
"github.com/ethereum/go-ethereum/consensus/ethash"
|
||||
"github.com/ethereum/go-ethereum/core/arena"
|
||||
"github.com/ethereum/go-ethereum/core/rawdb"
|
||||
"github.com/ethereum/go-ethereum/core/state"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/core/vm"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
"github.com/ethereum/go-ethereum/trie"
|
||||
)
|
||||
|
||||
// TestProcessWithBumpAllocator generates a chain with several types of
|
||||
// transactions, then processes each block with both HeapAllocator and
|
||||
// BumpAllocator, verifying that the state roots, receipt roots, gas used,
|
||||
// and logs match exactly.
|
||||
func TestProcessWithBumpAllocator(t *testing.T) {
|
||||
var (
|
||||
key1, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
|
||||
key2, _ = crypto.HexToECDSA("0202020202020202020202020202020202020202020202020202002020202020")
|
||||
addr1 = crypto.PubkeyToAddress(key1.PublicKey)
|
||||
addr2 = crypto.PubkeyToAddress(key2.PublicKey)
|
||||
config = params.MergedTestChainConfig
|
||||
signer = types.LatestSigner(config)
|
||||
gspec = &Genesis{
|
||||
Config: config,
|
||||
Alloc: types.GenesisAlloc{
|
||||
addr1: {Balance: big.NewInt(1_000_000_000_000_000_000)},
|
||||
addr2: {Balance: big.NewInt(1_000_000_000_000_000_000)},
|
||||
},
|
||||
}
|
||||
engine = beacon.New(ethash.NewFaker())
|
||||
)
|
||||
|
||||
// Generate a chain with diverse transactions: value transfers, contract
|
||||
// creates, and dynamic fee transactions.
|
||||
_, blocks, receipts := GenerateChainWithGenesis(gspec, engine, 5, func(i int, gen *BlockGen) {
|
||||
switch i {
|
||||
case 0:
|
||||
// Simple value transfer.
|
||||
tx, _ := types.SignTx(
|
||||
types.NewTransaction(gen.TxNonce(addr1), addr2, big.NewInt(1000), params.TxGas, gen.BaseFee(), nil),
|
||||
signer, key1,
|
||||
)
|
||||
gen.AddTx(tx)
|
||||
|
||||
case 1:
|
||||
// Two value transfers in one block.
|
||||
tx1, _ := types.SignTx(
|
||||
types.NewTransaction(gen.TxNonce(addr1), addr2, big.NewInt(2000), params.TxGas, gen.BaseFee(), nil),
|
||||
signer, key1,
|
||||
)
|
||||
gen.AddTx(tx1)
|
||||
tx2, _ := types.SignTx(
|
||||
types.NewTransaction(gen.TxNonce(addr2), addr1, big.NewInt(500), params.TxGas, gen.BaseFee(), nil),
|
||||
signer, key2,
|
||||
)
|
||||
gen.AddTx(tx2)
|
||||
|
||||
case 2:
|
||||
// Contract creation: deploy a minimal contract (PUSH1 0x42, PUSH1 0, MSTORE, PUSH1 1, PUSH1 31, RETURN).
|
||||
initCode := []byte{
|
||||
0x60, 0x42, // PUSH1 0x42
|
||||
0x60, 0x00, // PUSH1 0
|
||||
0x52, // MSTORE
|
||||
0x60, 0x01, // PUSH1 1
|
||||
0x60, 0x1f, // PUSH1 31
|
||||
0xf3, // RETURN
|
||||
}
|
||||
tx, _ := types.SignTx(
|
||||
types.NewContractCreation(gen.TxNonce(addr1), big.NewInt(0), 100_000, gen.BaseFee(), initCode),
|
||||
signer, key1,
|
||||
)
|
||||
gen.AddTx(tx)
|
||||
|
||||
case 3:
|
||||
// Dynamic fee transaction (EIP-1559).
|
||||
tx, _ := types.SignTx(types.NewTx(&types.DynamicFeeTx{
|
||||
Nonce: gen.TxNonce(addr1),
|
||||
GasTipCap: big.NewInt(1),
|
||||
GasFeeCap: new(big.Int).Add(gen.BaseFee(), big.NewInt(1)),
|
||||
Gas: params.TxGas,
|
||||
To: &addr2,
|
||||
Value: big.NewInt(3000),
|
||||
}), signer, key1)
|
||||
gen.AddTx(tx)
|
||||
|
||||
case 4:
|
||||
// Multiple dynamic fee txs from different senders.
|
||||
tx1, _ := types.SignTx(types.NewTx(&types.DynamicFeeTx{
|
||||
Nonce: gen.TxNonce(addr1),
|
||||
GasTipCap: big.NewInt(2),
|
||||
GasFeeCap: new(big.Int).Add(gen.BaseFee(), big.NewInt(2)),
|
||||
Gas: params.TxGas,
|
||||
To: &addr2,
|
||||
Value: big.NewInt(100),
|
||||
}), signer, key1)
|
||||
gen.AddTx(tx1)
|
||||
tx2, _ := types.SignTx(types.NewTx(&types.DynamicFeeTx{
|
||||
Nonce: gen.TxNonce(addr2),
|
||||
GasTipCap: big.NewInt(1),
|
||||
GasFeeCap: new(big.Int).Add(gen.BaseFee(), big.NewInt(1)),
|
||||
Gas: params.TxGas,
|
||||
To: &addr1,
|
||||
Value: big.NewInt(200),
|
||||
}), signer, key2)
|
||||
gen.AddTx(tx2)
|
||||
}
|
||||
})
|
||||
|
||||
// Sanity-check that we actually generated transactions.
|
||||
totalTxs := 0
|
||||
for _, r := range receipts {
|
||||
totalTxs += len(r)
|
||||
}
|
||||
if totalTxs == 0 {
|
||||
t.Fatal("no transactions generated")
|
||||
}
|
||||
t.Logf("generated %d blocks with %d total transactions", len(blocks), totalTxs)
|
||||
|
||||
// processBlocks runs the state processor on all blocks with the given
|
||||
// vm.Config and returns per-block state roots, receipt roots, gas used.
|
||||
type blockResult struct {
|
||||
stateRoot common.Hash
|
||||
receiptRoot common.Hash
|
||||
gasUsed uint64
|
||||
logCount int
|
||||
}
|
||||
|
||||
processBlocks := func(cfg vm.Config) ([]blockResult, error) {
|
||||
// Build a fresh blockchain from genesis.
|
||||
db := rawdb.NewMemoryDatabase()
|
||||
triedb := state.NewDatabaseForTesting()
|
||||
gspec.MustCommit(db, triedb.TrieDB())
|
||||
chain := &HeaderChain{
|
||||
config: gspec.Config,
|
||||
chainDb: db,
|
||||
engine: engine,
|
||||
}
|
||||
processor := NewStateProcessor(chain)
|
||||
|
||||
results := make([]blockResult, len(blocks))
|
||||
parentRoot := gspec.ToBlock().Root()
|
||||
|
||||
for i, block := range blocks {
|
||||
statedb, err := state.New(parentRoot, triedb)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res, err := processor.Process(context.Background(), block, statedb, cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
root := statedb.IntermediateRoot(gspec.Config.IsEIP158(block.Number()))
|
||||
receiptRoot := types.DeriveSha(res.Receipts, trie.NewStackTrie(nil))
|
||||
|
||||
results[i] = blockResult{
|
||||
stateRoot: root,
|
||||
receiptRoot: receiptRoot,
|
||||
gasUsed: res.GasUsed,
|
||||
logCount: len(res.Logs),
|
||||
}
|
||||
// Commit so the next block can read from it.
|
||||
statedb.Commit(block.NumberU64(), gspec.Config.IsEIP158(block.Number()), false)
|
||||
parentRoot = root
|
||||
}
|
||||
return results, nil
|
||||
}
|
||||
|
||||
// Process with HeapAllocator (default).
|
||||
heapResults, err := processBlocks(vm.Config{})
|
||||
if err != nil {
|
||||
t.Fatalf("heap processing failed: %v", err)
|
||||
}
|
||||
|
||||
// Process with BumpAllocator.
|
||||
slab := make([]byte, 32<<20) // 32 MiB
|
||||
bumpResults, err := processBlocks(vm.Config{
|
||||
Allocator: arena.NewBumpAllocator(slab),
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("bump processing failed: %v", err)
|
||||
}
|
||||
|
||||
// Compare results block by block.
|
||||
for i := range heapResults {
|
||||
h, b := heapResults[i], bumpResults[i]
|
||||
if h.stateRoot != b.stateRoot {
|
||||
t.Errorf("block %d: state root mismatch: heap=%x bump=%x", i, h.stateRoot, b.stateRoot)
|
||||
}
|
||||
if h.receiptRoot != b.receiptRoot {
|
||||
t.Errorf("block %d: receipt root mismatch: heap=%x bump=%x", i, h.receiptRoot, b.receiptRoot)
|
||||
}
|
||||
if h.gasUsed != b.gasUsed {
|
||||
t.Errorf("block %d: gas used mismatch: heap=%d bump=%d", i, h.gasUsed, b.gasUsed)
|
||||
}
|
||||
if h.logCount != b.logCount {
|
||||
t.Errorf("block %d: log count mismatch: heap=%d bump=%d", i, h.logCount, b.logCount)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestProcessWithBumpAllocatorResetBetweenBlocks verifies that the arena is
|
||||
// properly reset between blocks — i.e., processing block N+1 doesn't corrupt
|
||||
// because block N's arena memory was reclaimed.
|
||||
func TestProcessWithBumpAllocatorResetBetweenBlocks(t *testing.T) {
|
||||
var (
|
||||
key, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
|
||||
addr = crypto.PubkeyToAddress(key.PublicKey)
|
||||
config = params.MergedTestChainConfig
|
||||
signer = types.LatestSigner(config)
|
||||
engine = beacon.New(ethash.NewFaker())
|
||||
gspec = &Genesis{
|
||||
Config: config,
|
||||
Alloc: types.GenesisAlloc{
|
||||
addr: {Balance: big.NewInt(1_000_000_000_000_000_000)},
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
// Generate 10 blocks, each with a transfer, to exercise repeated
|
||||
// alloc/reset cycles on the same slab.
|
||||
_, blocks, _ := GenerateChainWithGenesis(gspec, engine, 10, func(i int, gen *BlockGen) {
|
||||
tx, _ := types.SignTx(
|
||||
types.NewTransaction(gen.TxNonce(addr), common.HexToAddress("0xdead"), big.NewInt(1), params.TxGas, gen.BaseFee(), nil),
|
||||
signer, key,
|
||||
)
|
||||
gen.AddTx(tx)
|
||||
})
|
||||
|
||||
// Use a deliberately small slab (512 KiB) so that without proper Reset,
|
||||
// the allocator would run out of memory across 10 blocks.
|
||||
slab := make([]byte, 512<<10)
|
||||
bumpAlloc := arena.NewBumpAllocator(slab)
|
||||
|
||||
db := rawdb.NewMemoryDatabase()
|
||||
triedb := state.NewDatabaseForTesting()
|
||||
gspec.MustCommit(db, triedb.TrieDB())
|
||||
|
||||
chain := &HeaderChain{
|
||||
config: gspec.Config,
|
||||
chainDb: db,
|
||||
engine: engine,
|
||||
}
|
||||
processor := NewStateProcessor(chain)
|
||||
parentRoot := gspec.ToBlock().Root()
|
||||
|
||||
for i, block := range blocks {
|
||||
statedb, err := state.New(parentRoot, triedb)
|
||||
if err != nil {
|
||||
t.Fatalf("block %d: state.New failed: %v", i, err)
|
||||
}
|
||||
cfg := vm.Config{Allocator: bumpAlloc}
|
||||
res, err := processor.Process(context.Background(), block, statedb, cfg)
|
||||
if err != nil {
|
||||
t.Fatalf("block %d: Process failed: %v", i, err)
|
||||
}
|
||||
if res.GasUsed == 0 {
|
||||
t.Fatalf("block %d: no gas used", i)
|
||||
}
|
||||
parentRoot = statedb.IntermediateRoot(gspec.Config.IsEIP158(block.Number()))
|
||||
statedb.Commit(block.NumberU64(), gspec.Config.IsEIP158(block.Number()), false)
|
||||
}
|
||||
}
|
||||
|
|
@ -2115,9 +2115,12 @@ func (bc *BlockChain) ProcessBlock(ctx context.Context, parentRoot common.Hash,
|
|||
}
|
||||
}()
|
||||
go func(start time.Time, throwaway *state.StateDB, block *types.Block) {
|
||||
// Disable tracing for prefetcher executions.
|
||||
// Disable tracing and arena allocator for prefetcher executions.
|
||||
// The arena allocator is not thread-safe and must not be shared
|
||||
// with the main processing goroutine.
|
||||
vmCfg := bc.cfg.VmConfig
|
||||
vmCfg.Tracer = nil
|
||||
vmCfg.Allocator = nil
|
||||
bc.prefetcher.Prefetch(block, throwaway, vmCfg, &interrupt)
|
||||
|
||||
blockPrefetchExecuteTimer.Update(time.Since(start))
|
||||
|
|
|
|||
19
core/evm.go
19
core/evm.go
|
|
@ -22,6 +22,7 @@ import (
|
|||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/consensus"
|
||||
"github.com/ethereum/go-ethereum/consensus/misc/eip4844"
|
||||
"github.com/ethereum/go-ethereum/core/arena"
|
||||
"github.com/ethereum/go-ethereum/core/tracing"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/core/vm"
|
||||
|
|
@ -39,6 +40,12 @@ type ChainContext interface {
|
|||
|
||||
// NewEVMBlockContext creates a new context for use in the EVM.
|
||||
func NewEVMBlockContext(header *types.Header, chain ChainContext, author *common.Address) vm.BlockContext {
|
||||
return NewEVMBlockContextWithAlloc(header, chain, author, arena.DefaultHeap)
|
||||
}
|
||||
|
||||
// NewEVMBlockContextWithAlloc creates a new context for use in the EVM, using
|
||||
// the provided allocator for transient big.Int allocations.
|
||||
func NewEVMBlockContextWithAlloc(header *types.Header, chain ChainContext, author *common.Address, alloc arena.Allocator) vm.BlockContext {
|
||||
var (
|
||||
beneficiary common.Address
|
||||
baseFee *big.Int
|
||||
|
|
@ -65,15 +72,17 @@ func NewEVMBlockContext(header *types.Header, chain ChainContext, author *common
|
|||
if header.SlotNumber != nil {
|
||||
slotNum = *header.SlotNumber
|
||||
}
|
||||
blockNumber := new(big.Int).Set(header.Number)
|
||||
difficulty := new(big.Int).Set(header.Difficulty)
|
||||
|
||||
return vm.BlockContext{
|
||||
CanTransfer: CanTransfer,
|
||||
Transfer: Transfer,
|
||||
GetHash: GetHashFn(header, chain),
|
||||
Coinbase: beneficiary,
|
||||
BlockNumber: new(big.Int).Set(header.Number),
|
||||
BlockNumber: blockNumber,
|
||||
Time: header.Time,
|
||||
Difficulty: new(big.Int).Set(header.Difficulty),
|
||||
Difficulty: difficulty,
|
||||
BaseFee: baseFee,
|
||||
BlobBaseFee: blobBaseFee,
|
||||
GasLimit: header.GasLimit,
|
||||
|
|
@ -84,6 +93,12 @@ func NewEVMBlockContext(header *types.Header, chain ChainContext, author *common
|
|||
|
||||
// NewEVMTxContext creates a new transaction context for a single transaction.
|
||||
func NewEVMTxContext(msg *Message) vm.TxContext {
|
||||
return NewEVMTxContextWithAlloc(msg, arena.DefaultHeap)
|
||||
}
|
||||
|
||||
// NewEVMTxContextWithAlloc creates a new transaction context, using the
|
||||
// provided allocator for transient big.Int allocations.
|
||||
func NewEVMTxContextWithAlloc(msg *Message, alloc arena.Allocator) vm.TxContext {
|
||||
ctx := vm.TxContext{
|
||||
Origin: msg.From,
|
||||
GasPrice: uint256.MustFromBig(msg.GasPrice),
|
||||
|
|
|
|||
|
|
@ -23,12 +23,14 @@ import (
|
|||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/consensus/misc"
|
||||
"github.com/ethereum/go-ethereum/core/arena"
|
||||
"github.com/ethereum/go-ethereum/core/state"
|
||||
"github.com/ethereum/go-ethereum/core/tracing"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/core/vm"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/internal/telemetry"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
)
|
||||
|
||||
|
|
@ -69,7 +71,14 @@ func (p *StateProcessor) Process(ctx context.Context, block *types.Block, stated
|
|||
blockNumber = block.Number()
|
||||
allLogs []*types.Log
|
||||
gp = new(GasPool).AddGas(block.GasLimit())
|
||||
alloc = cfg.GetAllocator()
|
||||
)
|
||||
defer func() {
|
||||
if ba, ok := alloc.(*arena.BumpAllocator); ok {
|
||||
log.Info("Arena usage after block", "used", common.StorageSize(ba.Used()), "peak", common.StorageSize(ba.Peak()), "slabs", ba.SlabCount(), "total", common.StorageSize(ba.TotalCapacity()))
|
||||
}
|
||||
alloc.Reset()
|
||||
}()
|
||||
|
||||
var tracingStateDB = vm.StateDB(statedb)
|
||||
if hooks := cfg.Tracer; hooks != nil {
|
||||
|
|
@ -86,7 +95,7 @@ func (p *StateProcessor) Process(ctx context.Context, block *types.Block, stated
|
|||
)
|
||||
|
||||
// Apply pre-execution system calls.
|
||||
context = NewEVMBlockContext(header, p.chain, nil)
|
||||
context = NewEVMBlockContextWithAlloc(header, p.chain, nil, alloc)
|
||||
evm := vm.NewEVM(context, tracingStateDB, config, cfg)
|
||||
|
||||
if beaconRoot := block.BeaconRoot(); beaconRoot != nil {
|
||||
|
|
@ -98,7 +107,7 @@ func (p *StateProcessor) Process(ctx context.Context, block *types.Block, stated
|
|||
|
||||
// Iterate over and process the individual transactions
|
||||
for i, tx := range block.Transactions() {
|
||||
msg, err := TransactionToMessage(tx, signer, header.BaseFee)
|
||||
msg, err := TransactionToMessageWithAlloc(tx, signer, header.BaseFee, alloc)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not apply tx %d [%v]: %w", i, tx.Hash().Hex(), err)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ import (
|
|||
"math/big"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/core/arena"
|
||||
"github.com/ethereum/go-ethereum/core/tracing"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/core/vm"
|
||||
|
|
@ -174,22 +175,27 @@ type Message struct {
|
|||
|
||||
// TransactionToMessage converts a transaction into a Message.
|
||||
func TransactionToMessage(tx *types.Transaction, s types.Signer, baseFee *big.Int) (*Message, error) {
|
||||
msg := &Message{
|
||||
Nonce: tx.Nonce(),
|
||||
GasLimit: tx.Gas(),
|
||||
GasPrice: tx.GasPrice(),
|
||||
GasFeeCap: tx.GasFeeCap(),
|
||||
GasTipCap: tx.GasTipCap(),
|
||||
To: tx.To(),
|
||||
Value: tx.Value(),
|
||||
Data: tx.Data(),
|
||||
AccessList: tx.AccessList(),
|
||||
SetCodeAuthorizations: tx.SetCodeAuthorizations(),
|
||||
SkipNonceChecks: false,
|
||||
SkipTransactionChecks: false,
|
||||
BlobHashes: tx.BlobHashes(),
|
||||
BlobGasFeeCap: tx.BlobGasFeeCap(),
|
||||
}
|
||||
return TransactionToMessageWithAlloc(tx, s, baseFee, arena.DefaultHeap)
|
||||
}
|
||||
|
||||
// TransactionToMessageWithAlloc converts a transaction into a Message, using
|
||||
// the provided allocator for the Message struct and its transient big.Int fields.
|
||||
func TransactionToMessageWithAlloc(tx *types.Transaction, s types.Signer, baseFee *big.Int, alloc arena.Allocator) (*Message, error) {
|
||||
msg := new(Message)
|
||||
msg.Nonce = tx.Nonce()
|
||||
msg.GasLimit = tx.Gas()
|
||||
msg.GasPrice = tx.GasPrice()
|
||||
msg.GasFeeCap = tx.GasFeeCap()
|
||||
msg.GasTipCap = tx.GasTipCap()
|
||||
|
||||
msg.To = tx.To()
|
||||
msg.Value = tx.Value()
|
||||
msg.Data = tx.Data()
|
||||
msg.AccessList = tx.AccessList()
|
||||
msg.SetCodeAuthorizations = tx.SetCodeAuthorizations()
|
||||
msg.BlobHashes = tx.BlobHashes()
|
||||
msg.BlobGasFeeCap = tx.BlobGasFeeCap()
|
||||
|
||||
// If baseFee provided, set gasPrice to effectiveGasPrice.
|
||||
if baseFee != nil {
|
||||
msg.GasPrice = msg.GasPrice.Add(msg.GasTipCap, baseFee)
|
||||
|
|
@ -210,7 +216,8 @@ func TransactionToMessage(tx *types.Transaction, s types.Signer, baseFee *big.In
|
|||
// indicates a core error meaning that the message would always fail for that particular
|
||||
// state and would never be accepted within a block.
|
||||
func ApplyMessage(evm *vm.EVM, msg *Message, gp *GasPool) (*ExecutionResult, error) {
|
||||
evm.SetTxContext(NewEVMTxContext(msg))
|
||||
alloc := evm.Config.GetAllocator()
|
||||
evm.SetTxContext(NewEVMTxContextWithAlloc(msg, alloc))
|
||||
return newStateTransition(evm, msg, gp).execute()
|
||||
}
|
||||
|
||||
|
|
@ -243,16 +250,19 @@ type stateTransition struct {
|
|||
initialGas uint64
|
||||
state vm.StateDB
|
||||
evm *vm.EVM
|
||||
alloc arena.Allocator
|
||||
}
|
||||
|
||||
// newStateTransition initialises and returns a new state transition object.
|
||||
func newStateTransition(evm *vm.EVM, msg *Message, gp *GasPool) *stateTransition {
|
||||
return &stateTransition{
|
||||
gp: gp,
|
||||
evm: evm,
|
||||
msg: msg,
|
||||
state: evm.StateDB,
|
||||
}
|
||||
alloc := evm.Config.GetAllocator()
|
||||
st := new(stateTransition)
|
||||
st.gp = gp
|
||||
st.evm = evm
|
||||
st.msg = msg
|
||||
st.state = evm.StateDB
|
||||
st.alloc = alloc
|
||||
return st
|
||||
}
|
||||
|
||||
// to returns the recipient of the message.
|
||||
|
|
@ -264,6 +274,8 @@ func (st *stateTransition) to() common.Address {
|
|||
}
|
||||
|
||||
func (st *stateTransition) buyGas() error {
|
||||
// Compute mgval = gasLimit * gasPrice (the gas cost to deduct from sender).
|
||||
// Use big.Int for balanceCheck to detect >256-bit overflow correctly.
|
||||
mgval := new(big.Int).SetUint64(st.msg.GasLimit)
|
||||
mgval.Mul(mgval, st.msg.GasPrice)
|
||||
balanceCheck := new(big.Int).Set(mgval)
|
||||
|
|
@ -544,11 +556,11 @@ func (st *stateTransition) execute() (*ExecutionResult, error) {
|
|||
}
|
||||
st.returnGas()
|
||||
|
||||
effectiveTip := msg.GasPrice
|
||||
effectiveTipU256, _ := uint256.FromBig(msg.GasPrice)
|
||||
if rules.IsLondon {
|
||||
effectiveTip = new(big.Int).Sub(msg.GasPrice, st.evm.Context.BaseFee)
|
||||
baseFee, _ := uint256.FromBig(st.evm.Context.BaseFee)
|
||||
effectiveTipU256 = new(uint256.Int).Sub(effectiveTipU256, baseFee)
|
||||
}
|
||||
effectiveTipU256, _ := uint256.FromBig(effectiveTip)
|
||||
|
||||
if st.evm.Config.NoBaseFee && msg.GasFeeCap.Sign() == 0 && msg.GasTipCap.Sign() == 0 {
|
||||
// Skip fee payment when NoBaseFee is set and the fee fields
|
||||
|
|
@ -565,12 +577,12 @@ func (st *stateTransition) execute() (*ExecutionResult, error) {
|
|||
}
|
||||
}
|
||||
|
||||
return &ExecutionResult{
|
||||
UsedGas: st.gasUsed(),
|
||||
MaxUsedGas: peakGasUsed,
|
||||
Err: vmerr,
|
||||
ReturnData: ret,
|
||||
}, nil
|
||||
result := new(ExecutionResult)
|
||||
result.UsedGas = st.gasUsed()
|
||||
result.MaxUsedGas = peakGasUsed
|
||||
result.Err = vmerr
|
||||
result.ReturnData = ret
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// validateAuthorization validates an EIP-7702 authorization against the state.
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ package vm
|
|||
|
||||
import (
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/core/arena"
|
||||
"github.com/ethereum/go-ethereum/core/tracing"
|
||||
"github.com/holiman/uint256"
|
||||
)
|
||||
|
|
@ -48,17 +49,23 @@ type Contract struct {
|
|||
|
||||
// NewContract returns a new contract environment for the execution of EVM.
|
||||
func NewContract(caller common.Address, address common.Address, value *uint256.Int, gas uint64, jumpDests JumpDestCache) *Contract {
|
||||
return NewContractWithAlloc(caller, address, value, gas, jumpDests, arena.DefaultHeap)
|
||||
}
|
||||
|
||||
// NewContractWithAlloc returns a new contract environment, allocating the
|
||||
// Contract struct from the provided allocator.
|
||||
func NewContractWithAlloc(caller common.Address, address common.Address, value *uint256.Int, gas uint64, jumpDests JumpDestCache, alloc arena.Allocator) *Contract {
|
||||
// Initialize the jump analysis cache if it's nil, mostly for tests
|
||||
if jumpDests == nil {
|
||||
jumpDests = newMapJumpDests()
|
||||
}
|
||||
return &Contract{
|
||||
caller: caller,
|
||||
address: address,
|
||||
jumpDests: jumpDests,
|
||||
Gas: gas,
|
||||
value: value,
|
||||
}
|
||||
c := new(Contract)
|
||||
c.caller = caller
|
||||
c.address = address
|
||||
c.jumpDests = jumpDests
|
||||
c.Gas = gas
|
||||
c.value = value
|
||||
return c
|
||||
}
|
||||
|
||||
func (c *Contract) validJumpdest(dest *uint256.Int) bool {
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ import (
|
|||
"sync/atomic"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/core/arena"
|
||||
"github.com/ethereum/go-ethereum/core/state"
|
||||
"github.com/ethereum/go-ethereum/core/tracing"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
|
|
@ -298,7 +299,8 @@ func (evm *EVM) Call(caller common.Address, addr common.Address, input []byte, g
|
|||
ret, err = nil, nil // gas is unchanged
|
||||
} else {
|
||||
// The contract is a scoped environment for this execution context only.
|
||||
contract := NewContract(caller, addr, value, gas, evm.jumpDests)
|
||||
alloc := evm.Config.GetAllocator()
|
||||
contract := NewContractWithAlloc(caller, addr, value, gas, evm.jumpDests, alloc)
|
||||
contract.IsSystemCall = isSystemCall(caller)
|
||||
contract.SetCallCode(evm.resolveCodeHash(addr), code)
|
||||
ret, err = evm.Run(contract, input, false)
|
||||
|
|
@ -361,7 +363,8 @@ func (evm *EVM) CallCode(caller common.Address, addr common.Address, input []byt
|
|||
} else {
|
||||
// Initialise a new contract and set the code that is to be used by the EVM.
|
||||
// The contract is a scoped environment for this execution context only.
|
||||
contract := NewContract(caller, caller, value, gas, evm.jumpDests)
|
||||
alloc := evm.Config.GetAllocator()
|
||||
contract := NewContractWithAlloc(caller, caller, value, gas, evm.jumpDests, alloc)
|
||||
contract.SetCallCode(evm.resolveCodeHash(addr), evm.resolveCode(addr))
|
||||
ret, err = evm.Run(contract, input, false)
|
||||
gas = contract.Gas
|
||||
|
|
@ -409,7 +412,8 @@ func (evm *EVM) DelegateCall(originCaller common.Address, caller common.Address,
|
|||
// Initialise a new contract and make initialise the delegate values
|
||||
//
|
||||
// Note: The value refers to the original value from the parent call.
|
||||
contract := NewContract(originCaller, caller, value, gas, evm.jumpDests)
|
||||
alloc := evm.Config.GetAllocator()
|
||||
contract := NewContractWithAlloc(originCaller, caller, value, gas, evm.jumpDests, alloc)
|
||||
contract.SetCallCode(evm.resolveCodeHash(addr), evm.resolveCode(addr))
|
||||
ret, err = evm.Run(contract, input, false)
|
||||
gas = contract.Gas
|
||||
|
|
@ -464,7 +468,9 @@ func (evm *EVM) StaticCall(caller common.Address, addr common.Address, input []b
|
|||
} else {
|
||||
// Initialise a new contract and set the code that is to be used by the EVM.
|
||||
// The contract is a scoped environment for this execution context only.
|
||||
contract := NewContract(caller, addr, new(uint256.Int), gas, evm.jumpDests)
|
||||
alloc := evm.Config.GetAllocator()
|
||||
zeroVal := arena.New[uint256.Int](alloc)
|
||||
contract := NewContractWithAlloc(caller, addr, zeroVal, gas, evm.jumpDests, alloc)
|
||||
contract.SetCallCode(evm.resolveCodeHash(addr), evm.resolveCode(addr))
|
||||
|
||||
// When an error was returned by the EVM or when setting the creation code
|
||||
|
|
@ -571,7 +577,8 @@ func (evm *EVM) create(caller common.Address, code []byte, gas uint64, value *ui
|
|||
|
||||
// Initialise a new contract and set the code that is to be used by the EVM.
|
||||
// The contract is a scoped environment for this execution context only.
|
||||
contract := NewContract(caller, address, value, gas, evm.jumpDests)
|
||||
alloc := evm.Config.GetAllocator()
|
||||
contract := NewContractWithAlloc(caller, address, value, gas, evm.jumpDests, alloc)
|
||||
|
||||
// Explicitly set the code to a null hash to prevent caching of jump analysis
|
||||
// for the initialization code.
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ import (
|
|||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/common/math"
|
||||
"github.com/ethereum/go-ethereum/core/arena"
|
||||
"github.com/ethereum/go-ethereum/core/tracing"
|
||||
"github.com/holiman/uint256"
|
||||
)
|
||||
|
|
@ -28,14 +29,24 @@ import (
|
|||
// Config are the configuration options for the Interpreter
|
||||
type Config struct {
|
||||
Tracer *tracing.Hooks
|
||||
NoBaseFee bool // Forces the EIP-1559 baseFee to 0 (needed for 0 price calls)
|
||||
EnablePreimageRecording bool // Enables recording of SHA3/keccak preimages
|
||||
ExtraEips []int // Additional EIPS that are to be enabled
|
||||
NoBaseFee bool // Forces the EIP-1559 baseFee to 0 (needed for 0 price calls)
|
||||
EnablePreimageRecording bool // Enables recording of SHA3/keccak preimages
|
||||
ExtraEips []int // Additional EIPS that are to be enabled
|
||||
Allocator arena.Allocator // Arena allocator; nil defaults to HeapAllocator
|
||||
|
||||
StatelessSelfValidation bool // Generate execution witnesses and self-check against them (testing purpose)
|
||||
EnableWitnessStats bool // Whether trie access statistics collection is enabled
|
||||
}
|
||||
|
||||
// GetAllocator returns the allocator from the config, defaulting to the
|
||||
// heap allocator if none is set.
|
||||
func (c Config) GetAllocator() arena.Allocator {
|
||||
if c.Allocator != nil {
|
||||
return c.Allocator
|
||||
}
|
||||
return arena.DefaultHeap
|
||||
}
|
||||
|
||||
// ScopeContext contains the things that are per-call, such as stack and memory,
|
||||
// but not transients like pc and gas
|
||||
type ScopeContext struct {
|
||||
|
|
@ -115,11 +126,12 @@ func (evm *EVM) Run(contract *Contract, input []byte, readOnly bool) (ret []byte
|
|||
return nil, nil
|
||||
}
|
||||
|
||||
alloc := evm.Config.GetAllocator()
|
||||
var (
|
||||
op OpCode // current opcode
|
||||
jumpTable *JumpTable = evm.table
|
||||
mem = NewMemory() // bound memory
|
||||
stack = newstack() // local stack
|
||||
mem = NewMemoryWithAlloc(alloc) // bound memory
|
||||
stack = newstackWithAlloc(alloc) // local stack
|
||||
callContext = &ScopeContext{
|
||||
Memory: mem,
|
||||
Stack: stack,
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ package vm
|
|||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/ethereum/go-ethereum/core/arena"
|
||||
"github.com/holiman/uint256"
|
||||
)
|
||||
|
||||
|
|
@ -32,6 +33,7 @@ var memoryPool = sync.Pool{
|
|||
type Memory struct {
|
||||
store []byte
|
||||
lastGasCost uint64
|
||||
arenaAlloc bool // true if allocated from a BumpAllocator (skip pool return)
|
||||
}
|
||||
|
||||
// NewMemory returns a new memory model.
|
||||
|
|
@ -39,8 +41,23 @@ func NewMemory() *Memory {
|
|||
return memoryPool.Get().(*Memory)
|
||||
}
|
||||
|
||||
// NewMemoryWithAlloc returns a new Memory, allocating from the provided arena.
|
||||
// When using a BumpAllocator, the Memory struct is arena-allocated and the pool
|
||||
// is bypassed. When using a HeapAllocator, it falls back to the pool.
|
||||
func NewMemoryWithAlloc(alloc arena.Allocator) *Memory {
|
||||
if _, ok := alloc.(*arena.BumpAllocator); ok {
|
||||
m := new(Memory)
|
||||
m.arenaAlloc = true
|
||||
return m
|
||||
}
|
||||
return NewMemory()
|
||||
}
|
||||
|
||||
// Free returns the memory to the pool.
|
||||
func (m *Memory) Free() {
|
||||
if m.arenaAlloc {
|
||||
return // arena-allocated: freed on arena.Reset()
|
||||
}
|
||||
// To reduce peak allocation, return only smaller memory instances to the pool.
|
||||
const maxBufferSize = 16 << 10
|
||||
if cap(m.store) <= maxBufferSize {
|
||||
|
|
|
|||
233
core/vm/runtime/arena_test.go
Normal file
233
core/vm/runtime/arena_test.go
Normal file
|
|
@ -0,0 +1,233 @@
|
|||
// Copyright 2026 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it 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 go-ethereum library is distributed in the hope that it 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 runtime
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"math/big"
|
||||
"testing"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/core/arena"
|
||||
"github.com/ethereum/go-ethereum/core/state"
|
||||
"github.com/ethereum/go-ethereum/core/tracing"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/core/vm"
|
||||
)
|
||||
|
||||
// TestExecuteWithBumpAllocator runs bytecode through the EVM with a
|
||||
// BumpAllocator and verifies the result matches the HeapAllocator path.
|
||||
func TestExecuteWithBumpAllocator(t *testing.T) {
|
||||
// Bytecode: PUSH1 0x2a, PUSH1 0, MSTORE, PUSH1 32, PUSH1 0, RETURN
|
||||
// Returns the 32-byte big-endian encoding of 42.
|
||||
code := []byte{
|
||||
byte(vm.PUSH1), 0x2a,
|
||||
byte(vm.PUSH1), 0,
|
||||
byte(vm.MSTORE),
|
||||
byte(vm.PUSH1), 32,
|
||||
byte(vm.PUSH1), 0,
|
||||
byte(vm.RETURN),
|
||||
}
|
||||
|
||||
// Run with heap allocator (default).
|
||||
heapRet, _, err := Execute(code, nil, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("heap execute failed: %v", err)
|
||||
}
|
||||
|
||||
// Run with bump allocator.
|
||||
slab := make([]byte, 8<<20) // 8 MiB
|
||||
bumpRet, _, err := Execute(code, nil, &Config{
|
||||
EVMConfig: vm.Config{
|
||||
Allocator: arena.NewBumpAllocator(slab),
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("bump execute failed: %v", err)
|
||||
}
|
||||
|
||||
if !bytes.Equal(heapRet, bumpRet) {
|
||||
t.Fatalf("results differ:\n heap: %x\n bump: %x", heapRet, bumpRet)
|
||||
}
|
||||
}
|
||||
|
||||
// TestCallWithBumpAllocator deploys a simple contract, then calls it using
|
||||
// both allocator types and verifies matching results.
|
||||
func TestCallWithBumpAllocator(t *testing.T) {
|
||||
// Contract code: returns the caller's address (CALLER, PUSH1 0, MSTORE, PUSH1 20, PUSH1 12, RETURN)
|
||||
code := []byte{
|
||||
byte(vm.CALLER),
|
||||
byte(vm.PUSH1), 0,
|
||||
byte(vm.MSTORE),
|
||||
byte(vm.PUSH1), 20,
|
||||
byte(vm.PUSH1), 12,
|
||||
byte(vm.RETURN),
|
||||
}
|
||||
address := common.HexToAddress("0xaa")
|
||||
|
||||
run := func(alloc arena.Allocator) ([]byte, error) {
|
||||
statedb, _ := state.New(types.EmptyRootHash, state.NewDatabaseForTesting())
|
||||
statedb.CreateAccount(address)
|
||||
statedb.SetCode(address, code, tracing.CodeChangeUnspecified)
|
||||
statedb.Finalise(true)
|
||||
|
||||
ret, _, err := Call(address, nil, &Config{
|
||||
State: statedb,
|
||||
EVMConfig: vm.Config{
|
||||
Allocator: alloc,
|
||||
},
|
||||
})
|
||||
return ret, err
|
||||
}
|
||||
|
||||
heapRet, err := run(nil)
|
||||
if err != nil {
|
||||
t.Fatalf("heap call failed: %v", err)
|
||||
}
|
||||
|
||||
slab := make([]byte, 8<<20)
|
||||
bumpRet, err := run(arena.NewBumpAllocator(slab))
|
||||
if err != nil {
|
||||
t.Fatalf("bump call failed: %v", err)
|
||||
}
|
||||
|
||||
if !bytes.Equal(heapRet, bumpRet) {
|
||||
t.Fatalf("results differ:\n heap: %x\n bump: %x", heapRet, bumpRet)
|
||||
}
|
||||
}
|
||||
|
||||
// TestNestedCallsWithBumpAllocator exercises nested EVM calls (CALL opcode)
|
||||
// with a BumpAllocator to stress-test per-call-frame arena allocation of
|
||||
// Contract, Memory, Stack, and ScopeContext.
|
||||
func TestNestedCallsWithBumpAllocator(t *testing.T) {
|
||||
// Inner contract: returns 0x42.
|
||||
innerCode := []byte{
|
||||
byte(vm.PUSH1), 0x42,
|
||||
byte(vm.PUSH1), 0,
|
||||
byte(vm.MSTORE),
|
||||
byte(vm.PUSH1), 32,
|
||||
byte(vm.PUSH1), 0,
|
||||
byte(vm.RETURN),
|
||||
}
|
||||
innerAddr := common.HexToAddress("0xbb")
|
||||
|
||||
// Outer contract calls inner and returns its result.
|
||||
// PUSH1 32 - retSize
|
||||
// PUSH1 0 - retOffset
|
||||
// PUSH1 0 - argsSize
|
||||
// PUSH1 0 - argsOffset
|
||||
// PUSH1 0 - value
|
||||
// PUSH20 <inner> - address
|
||||
// PUSH3 0xffffff - gas
|
||||
// CALL
|
||||
// PUSH1 32 - size
|
||||
// PUSH1 0 - offset
|
||||
// RETURN
|
||||
outerCode := []byte{
|
||||
byte(vm.PUSH1), 32,
|
||||
byte(vm.PUSH1), 0,
|
||||
byte(vm.PUSH1), 0,
|
||||
byte(vm.PUSH1), 0,
|
||||
byte(vm.PUSH1), 0,
|
||||
byte(vm.PUSH20),
|
||||
}
|
||||
outerCode = append(outerCode, innerAddr.Bytes()...)
|
||||
outerCode = append(outerCode,
|
||||
byte(vm.PUSH3), 0xff, 0xff, 0xff,
|
||||
byte(vm.CALL),
|
||||
byte(vm.POP), // pop success flag
|
||||
byte(vm.PUSH1), 32,
|
||||
byte(vm.PUSH1), 0,
|
||||
byte(vm.RETURN),
|
||||
)
|
||||
outerAddr := common.HexToAddress("0xcc")
|
||||
|
||||
run := func(alloc arena.Allocator) ([]byte, error) {
|
||||
statedb, _ := state.New(types.EmptyRootHash, state.NewDatabaseForTesting())
|
||||
statedb.CreateAccount(innerAddr)
|
||||
statedb.SetCode(innerAddr, innerCode, tracing.CodeChangeUnspecified)
|
||||
statedb.CreateAccount(outerAddr)
|
||||
statedb.SetCode(outerAddr, outerCode, tracing.CodeChangeUnspecified)
|
||||
statedb.Finalise(true)
|
||||
|
||||
ret, _, err := Call(outerAddr, nil, &Config{
|
||||
State: statedb,
|
||||
EVMConfig: vm.Config{
|
||||
Allocator: alloc,
|
||||
},
|
||||
})
|
||||
return ret, err
|
||||
}
|
||||
|
||||
heapRet, err := run(nil)
|
||||
if err != nil {
|
||||
t.Fatalf("heap nested call failed: %v", err)
|
||||
}
|
||||
|
||||
slab := make([]byte, 8<<20)
|
||||
bumpRet, err := run(arena.NewBumpAllocator(slab))
|
||||
if err != nil {
|
||||
t.Fatalf("bump nested call failed: %v", err)
|
||||
}
|
||||
|
||||
if !bytes.Equal(heapRet, bumpRet) {
|
||||
t.Fatalf("nested call results differ:\n heap: %x\n bump: %x", heapRet, bumpRet)
|
||||
}
|
||||
}
|
||||
|
||||
// TestCreateWithBumpAllocator exercises contract creation (CREATE opcode)
|
||||
// with a BumpAllocator.
|
||||
func TestCreateWithBumpAllocator(t *testing.T) {
|
||||
// Simple init code that returns 0x42 as runtime code.
|
||||
initCode := []byte{
|
||||
byte(vm.PUSH1), 0x42, // runtime code is just "0x42" (1 byte)
|
||||
byte(vm.PUSH1), 0,
|
||||
byte(vm.MSTORE),
|
||||
// Return 1 byte from memory offset 31 (the 0x42 byte in big-endian word).
|
||||
byte(vm.PUSH1), 1,
|
||||
byte(vm.PUSH1), 31,
|
||||
byte(vm.RETURN),
|
||||
}
|
||||
|
||||
run := func(alloc arena.Allocator) ([]byte, common.Address, error) {
|
||||
code, addr, _, err := Create(initCode, &Config{
|
||||
EVMConfig: vm.Config{
|
||||
Allocator: alloc,
|
||||
},
|
||||
Value: big.NewInt(0),
|
||||
})
|
||||
return code, addr, err
|
||||
}
|
||||
|
||||
heapCode, heapAddr, err := run(nil)
|
||||
if err != nil {
|
||||
t.Fatalf("heap create failed: %v", err)
|
||||
}
|
||||
|
||||
slab := make([]byte, 8<<20)
|
||||
bumpCode, bumpAddr, err := run(arena.NewBumpAllocator(slab))
|
||||
if err != nil {
|
||||
t.Fatalf("bump create failed: %v", err)
|
||||
}
|
||||
|
||||
if !bytes.Equal(heapCode, bumpCode) {
|
||||
t.Fatalf("created code differs:\n heap: %x\n bump: %x", heapCode, bumpCode)
|
||||
}
|
||||
if heapAddr != bumpAddr {
|
||||
t.Fatalf("created address differs: heap=%s bump=%s", heapAddr, bumpAddr)
|
||||
}
|
||||
}
|
||||
|
|
@ -19,6 +19,7 @@ package vm
|
|||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/ethereum/go-ethereum/core/arena"
|
||||
"github.com/holiman/uint256"
|
||||
)
|
||||
|
||||
|
|
@ -32,14 +33,32 @@ var stackPool = sync.Pool{
|
|||
// expected to be changed and modified. stack does not take care of adding newly
|
||||
// initialized objects.
|
||||
type Stack struct {
|
||||
data []uint256.Int
|
||||
data []uint256.Int
|
||||
arenaAlloc bool // true if allocated from a BumpAllocator (skip pool return)
|
||||
}
|
||||
|
||||
func newstack() *Stack {
|
||||
return stackPool.Get().(*Stack)
|
||||
}
|
||||
|
||||
// newstackWithAlloc returns a new Stack, allocating from the provided arena.
|
||||
// When using a BumpAllocator, the Stack struct is arena-allocated and the
|
||||
// backing data slice comes from the arena. When using a HeapAllocator, it
|
||||
// falls back to the pool.
|
||||
func newstackWithAlloc(alloc arena.Allocator) *Stack {
|
||||
if _, ok := alloc.(*arena.BumpAllocator); ok {
|
||||
s := new(Stack)
|
||||
s.data = arena.MakeSlice[uint256.Int](alloc, 0, 1024)
|
||||
s.arenaAlloc = true
|
||||
return s
|
||||
}
|
||||
return newstack()
|
||||
}
|
||||
|
||||
func returnStack(s *Stack) {
|
||||
if s.arenaAlloc {
|
||||
return // arena-allocated: freed on arena.Reset()
|
||||
}
|
||||
s.data = s.data[:0]
|
||||
stackPool.Put(s)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@ import (
|
|||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
"github.com/ethereum/go-ethereum/consensus"
|
||||
"github.com/ethereum/go-ethereum/core"
|
||||
"github.com/ethereum/go-ethereum/core/arena"
|
||||
"github.com/ethereum/go-ethereum/core/filtermaps"
|
||||
"github.com/ethereum/go-ethereum/core/rawdb"
|
||||
"github.com/ethereum/go-ethereum/core/state/pruner"
|
||||
|
|
@ -239,6 +240,7 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) {
|
|||
EnablePreimageRecording: config.EnablePreimageRecording,
|
||||
EnableWitnessStats: config.EnableWitnessStats,
|
||||
StatelessSelfValidation: config.StatelessSelfValidation,
|
||||
Allocator: newAllocatorFromConfig(config),
|
||||
},
|
||||
// Enables file journaling for the trie database. The journal files will be stored
|
||||
// within the data directory. The corresponding paths will be either:
|
||||
|
|
@ -597,3 +599,12 @@ func (s *Ethereum) Stop() error {
|
|||
|
||||
return nil
|
||||
}
|
||||
|
||||
// newAllocatorFromConfig returns a BumpAllocator if arena allocation is enabled
|
||||
// in the config, or nil (which defaults to HeapAllocator) otherwise.
|
||||
func newAllocatorFromConfig(config *ethconfig.Config) arena.Allocator {
|
||||
if config.EnableArenaAlloc {
|
||||
return arena.NewBumpAllocator(make([]byte, 8<<20)) // 8 MiB initial slab (grows automatically)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -174,6 +174,9 @@ type Config struct {
|
|||
// Generate execution witnesses and self-check against them (testing purpose)
|
||||
StatelessSelfValidation bool
|
||||
|
||||
// Use bump/arena allocator for transient EVM allocations during block processing
|
||||
EnableArenaAlloc bool
|
||||
|
||||
// Enables tracking of state size
|
||||
EnableStateSizeTracking bool
|
||||
|
||||
|
|
|
|||
BIN
geth
Executable file
BIN
geth
Executable file
Binary file not shown.
BIN
keeper-zisk.elf
Normal file
BIN
keeper-zisk.elf
Normal file
Binary file not shown.
Loading…
Reference in a new issue