mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-07-19 11:20:45 +00:00
Merge 56b9933da8 into fe3a74e610
This commit is contained in:
commit
cc16e93193
28 changed files with 1320 additions and 135 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))
|
||||
|
|
|
|||
21
core/evm.go
21
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,9 +93,15 @@ 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),
|
||||
GasPrice: new(uint256.Int).Set(&msg.GasPrice),
|
||||
BlobHashes: msg.BlobHashes,
|
||||
}
|
||||
return ctx
|
||||
|
|
|
|||
|
|
@ -23,13 +23,16 @@ 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"
|
||||
"github.com/holiman/uint256"
|
||||
)
|
||||
|
||||
// StateProcessor is a basic Processor, which takes care of transitioning
|
||||
|
|
@ -69,7 +72,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 +96,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 +108,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)
|
||||
}
|
||||
|
|
@ -247,9 +257,9 @@ func ProcessBeaconBlockRoot(beaconRoot common.Hash, evm *vm.EVM) {
|
|||
msg := &Message{
|
||||
From: params.SystemAddress,
|
||||
GasLimit: 30_000_000,
|
||||
GasPrice: common.Big0,
|
||||
GasFeeCap: common.Big0,
|
||||
GasTipCap: common.Big0,
|
||||
GasPrice: uint256.Int{},
|
||||
GasFeeCap: uint256.Int{},
|
||||
GasTipCap: uint256.Int{},
|
||||
To: ¶ms.BeaconRootsAddress,
|
||||
Data: beaconRoot[:],
|
||||
}
|
||||
|
|
@ -271,9 +281,9 @@ func ProcessParentBlockHash(prevHash common.Hash, evm *vm.EVM) {
|
|||
msg := &Message{
|
||||
From: params.SystemAddress,
|
||||
GasLimit: 30_000_000,
|
||||
GasPrice: common.Big0,
|
||||
GasFeeCap: common.Big0,
|
||||
GasTipCap: common.Big0,
|
||||
GasPrice: uint256.Int{},
|
||||
GasFeeCap: uint256.Int{},
|
||||
GasTipCap: uint256.Int{},
|
||||
To: ¶ms.HistoryStorageAddress,
|
||||
Data: prevHash.Bytes(),
|
||||
}
|
||||
|
|
@ -311,9 +321,9 @@ func processRequestsSystemCall(requests *[][]byte, evm *vm.EVM, requestType byte
|
|||
msg := &Message{
|
||||
From: params.SystemAddress,
|
||||
GasLimit: 30_000_000,
|
||||
GasPrice: common.Big0,
|
||||
GasFeeCap: common.Big0,
|
||||
GasTipCap: common.Big0,
|
||||
GasPrice: uint256.Int{},
|
||||
GasFeeCap: uint256.Int{},
|
||||
GasTipCap: uint256.Int{},
|
||||
To: &addr,
|
||||
}
|
||||
evm.SetTxContext(NewEVMTxContext(msg))
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
@ -146,14 +147,14 @@ type Message struct {
|
|||
To *common.Address
|
||||
From common.Address
|
||||
Nonce uint64
|
||||
Value *big.Int
|
||||
Value uint256.Int
|
||||
GasLimit uint64
|
||||
GasPrice *big.Int
|
||||
GasFeeCap *big.Int
|
||||
GasTipCap *big.Int
|
||||
GasPrice uint256.Int
|
||||
GasFeeCap uint256.Int
|
||||
GasTipCap uint256.Int
|
||||
Data []byte
|
||||
AccessList types.AccessList
|
||||
BlobGasFeeCap *big.Int
|
||||
BlobGasFeeCap uint256.Int
|
||||
BlobHashes []common.Hash
|
||||
SetCodeAuthorizations []types.SetCodeAuthorization
|
||||
|
||||
|
|
@ -174,32 +175,68 @@ 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(),
|
||||
}
|
||||
// If baseFee provided, set gasPrice to effectiveGasPrice.
|
||||
if baseFee != nil {
|
||||
msg.GasPrice = msg.GasPrice.Add(msg.GasTipCap, baseFee)
|
||||
if msg.GasPrice.Cmp(msg.GasFeeCap) > 0 {
|
||||
msg.GasPrice = msg.GasFeeCap
|
||||
}
|
||||
}
|
||||
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 := arena.New[Message](alloc)
|
||||
|
||||
var err error
|
||||
msg.From, err = types.Sender(s, tx)
|
||||
return msg, err
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
msg.Nonce = tx.Nonce()
|
||||
msg.GasLimit = tx.Gas()
|
||||
|
||||
var (
|
||||
v *uint256.Int
|
||||
overflow bool
|
||||
)
|
||||
if v, overflow = uint256.FromBig(tx.GasPrice()); overflow {
|
||||
return nil, fmt.Errorf("%w: address %v, maxFeePerGas bit length: %d", ErrFeeCapVeryHigh,
|
||||
msg.From.Hex(), tx.GasPrice().BitLen())
|
||||
}
|
||||
msg.GasPrice = *v
|
||||
if v, overflow = uint256.FromBig(tx.GasFeeCap()); overflow {
|
||||
return nil, fmt.Errorf("%w: address %v, maxFeePerGas bit length: %d", ErrFeeCapVeryHigh,
|
||||
msg.From.Hex(), tx.GasFeeCap().BitLen())
|
||||
}
|
||||
msg.GasFeeCap = *v
|
||||
if v, overflow = uint256.FromBig(tx.GasTipCap()); overflow {
|
||||
return nil, fmt.Errorf("%w: address %v, maxPriorityFeePerGas bit length: %d", ErrTipVeryHigh,
|
||||
msg.From.Hex(), tx.GasTipCap().BitLen())
|
||||
}
|
||||
msg.GasTipCap = *v
|
||||
|
||||
msg.To = tx.To()
|
||||
if v, overflow = uint256.FromBig(tx.Value()); overflow {
|
||||
return nil, fmt.Errorf("%w: address %v", ErrInsufficientFundsForTransfer, msg.From.Hex())
|
||||
}
|
||||
msg.Value = *v
|
||||
msg.Data = tx.Data()
|
||||
msg.AccessList = tx.AccessList()
|
||||
msg.SetCodeAuthorizations = tx.SetCodeAuthorizations()
|
||||
msg.BlobHashes = tx.BlobHashes()
|
||||
if tx.BlobGasFeeCap() != nil {
|
||||
if v, overflow = uint256.FromBig(tx.BlobGasFeeCap()); overflow {
|
||||
return nil, fmt.Errorf("%w: blobGasFeeCap exceeds 256 bits", ErrBlobFeeCapTooLow)
|
||||
}
|
||||
msg.BlobGasFeeCap = *v
|
||||
}
|
||||
|
||||
// If baseFee provided, set gasPrice to effectiveGasPrice.
|
||||
if baseFee != nil {
|
||||
baseFeeU256 := uint256.MustFromBig(baseFee)
|
||||
msg.GasPrice.Add(&msg.GasTipCap, baseFeeU256)
|
||||
if msg.GasPrice.Cmp(&msg.GasFeeCap) > 0 {
|
||||
msg.GasPrice.Set(&msg.GasFeeCap)
|
||||
}
|
||||
}
|
||||
return msg, nil
|
||||
}
|
||||
|
||||
// ApplyMessage computes the new state by applying the given message
|
||||
|
|
@ -210,7 +247,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 +281,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,20 +305,21 @@ func (st *stateTransition) to() common.Address {
|
|||
}
|
||||
|
||||
func (st *stateTransition) buyGas() error {
|
||||
// Use big.Int for balance check to detect >256-bit overflow correctly.
|
||||
mgval := new(big.Int).SetUint64(st.msg.GasLimit)
|
||||
mgval.Mul(mgval, st.msg.GasPrice)
|
||||
mgval.Mul(mgval, st.msg.GasPrice.ToBig())
|
||||
balanceCheck := new(big.Int).Set(mgval)
|
||||
if st.msg.GasFeeCap != nil {
|
||||
if !st.msg.GasFeeCap.IsZero() {
|
||||
balanceCheck.SetUint64(st.msg.GasLimit)
|
||||
balanceCheck = balanceCheck.Mul(balanceCheck, st.msg.GasFeeCap)
|
||||
balanceCheck.Mul(balanceCheck, st.msg.GasFeeCap.ToBig())
|
||||
}
|
||||
balanceCheck.Add(balanceCheck, st.msg.Value)
|
||||
balanceCheck.Add(balanceCheck, st.msg.Value.ToBig())
|
||||
|
||||
if st.evm.ChainConfig().IsCancun(st.evm.Context.BlockNumber, st.evm.Context.Time) {
|
||||
if blobGas := st.blobGasUsed(); blobGas > 0 {
|
||||
// Check that the user has enough funds to cover blobGasUsed * tx.BlobGasFeeCap
|
||||
blobBalanceCheck := new(big.Int).SetUint64(blobGas)
|
||||
blobBalanceCheck.Mul(blobBalanceCheck, st.msg.BlobGasFeeCap)
|
||||
blobBalanceCheck.Mul(blobBalanceCheck, st.msg.BlobGasFeeCap.ToBig())
|
||||
balanceCheck.Add(balanceCheck, blobBalanceCheck)
|
||||
// Pay for blobGasUsed * actual blob fee
|
||||
blobFee := new(big.Int).SetUint64(blobGas)
|
||||
|
|
@ -340,25 +382,18 @@ func (st *stateTransition) preCheck() error {
|
|||
// Make sure that transaction gasFeeCap is greater than the baseFee (post london)
|
||||
if st.evm.ChainConfig().IsLondon(st.evm.Context.BlockNumber) {
|
||||
// Skip the checks if gas fields are zero and baseFee was explicitly disabled (eth_call)
|
||||
skipCheck := st.evm.Config.NoBaseFee && msg.GasFeeCap.BitLen() == 0 && msg.GasTipCap.BitLen() == 0
|
||||
skipCheck := st.evm.Config.NoBaseFee && msg.GasFeeCap.IsZero() && msg.GasTipCap.IsZero()
|
||||
if !skipCheck {
|
||||
if l := msg.GasFeeCap.BitLen(); l > 256 {
|
||||
return fmt.Errorf("%w: address %v, maxFeePerGas bit length: %d", ErrFeeCapVeryHigh,
|
||||
msg.From.Hex(), l)
|
||||
}
|
||||
if l := msg.GasTipCap.BitLen(); l > 256 {
|
||||
return fmt.Errorf("%w: address %v, maxPriorityFeePerGas bit length: %d", ErrTipVeryHigh,
|
||||
msg.From.Hex(), l)
|
||||
}
|
||||
if msg.GasFeeCap.Cmp(msg.GasTipCap) < 0 {
|
||||
if msg.GasFeeCap.Cmp(&msg.GasTipCap) < 0 {
|
||||
return fmt.Errorf("%w: address %v, maxPriorityFeePerGas: %s, maxFeePerGas: %s", ErrTipAboveFeeCap,
|
||||
msg.From.Hex(), msg.GasTipCap, msg.GasFeeCap)
|
||||
msg.From.Hex(), &msg.GasTipCap, &msg.GasFeeCap)
|
||||
}
|
||||
// This will panic if baseFee is nil, but basefee presence is verified
|
||||
// as part of header validation.
|
||||
if msg.GasFeeCap.Cmp(st.evm.Context.BaseFee) < 0 {
|
||||
baseFeeU256 := uint256.MustFromBig(st.evm.Context.BaseFee)
|
||||
if msg.GasFeeCap.Cmp(baseFeeU256) < 0 {
|
||||
return fmt.Errorf("%w: address %v, maxFeePerGas: %s, baseFee: %s", ErrFeeCapTooLow,
|
||||
msg.From.Hex(), msg.GasFeeCap, st.evm.Context.BaseFee)
|
||||
msg.From.Hex(), &msg.GasFeeCap, st.evm.Context.BaseFee)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -386,11 +421,12 @@ func (st *stateTransition) preCheck() error {
|
|||
if st.evm.ChainConfig().IsCancun(st.evm.Context.BlockNumber, st.evm.Context.Time) {
|
||||
if st.blobGasUsed() > 0 {
|
||||
// Skip the checks if gas fields are zero and blobBaseFee was explicitly disabled (eth_call)
|
||||
skipCheck := st.evm.Config.NoBaseFee && msg.BlobGasFeeCap.BitLen() == 0
|
||||
skipCheck := st.evm.Config.NoBaseFee && msg.BlobGasFeeCap.IsZero()
|
||||
if !skipCheck {
|
||||
// This will panic if blobBaseFee is nil, but blobBaseFee presence
|
||||
// is verified as part of header validation.
|
||||
if msg.BlobGasFeeCap.Cmp(st.evm.Context.BlobBaseFee) < 0 {
|
||||
blobBaseFeeU256 := uint256.MustFromBig(st.evm.Context.BlobBaseFee)
|
||||
if msg.BlobGasFeeCap.Cmp(blobBaseFeeU256) < 0 {
|
||||
return fmt.Errorf("%w: address %v blobGasFeeCap: %v, blobBaseFee: %v", ErrBlobFeeCapTooLow,
|
||||
msg.From.Hex(), msg.BlobGasFeeCap, st.evm.Context.BlobBaseFee)
|
||||
}
|
||||
|
|
@ -474,11 +510,7 @@ func (st *stateTransition) execute() (*ExecutionResult, error) {
|
|||
}
|
||||
|
||||
// Check clause 6
|
||||
value, overflow := uint256.FromBig(msg.Value)
|
||||
if overflow {
|
||||
return nil, fmt.Errorf("%w: address %v", ErrInsufficientFundsForTransfer, msg.From.Hex())
|
||||
}
|
||||
if !value.IsZero() && !st.evm.Context.CanTransfer(st.state, msg.From, value) {
|
||||
if !msg.Value.IsZero() && !st.evm.Context.CanTransfer(st.state, msg.From, &msg.Value) {
|
||||
return nil, fmt.Errorf("%w: address %v", ErrInsufficientFundsForTransfer, msg.From.Hex())
|
||||
}
|
||||
|
||||
|
|
@ -497,7 +529,7 @@ func (st *stateTransition) execute() (*ExecutionResult, error) {
|
|||
vmerr error // vm errors do not effect consensus and are therefore not assigned to err
|
||||
)
|
||||
if contractCreation {
|
||||
ret, _, st.gasRemaining, vmerr = st.evm.Create(msg.From, msg.Data, st.gasRemaining, value)
|
||||
ret, _, st.gasRemaining, vmerr = st.evm.Create(msg.From, msg.Data, st.gasRemaining, &msg.Value)
|
||||
} else {
|
||||
// Increment the nonce for the next transaction.
|
||||
st.state.SetNonce(msg.From, st.state.GetNonce(msg.From)+1, tracing.NonceChangeEoACall)
|
||||
|
|
@ -520,7 +552,7 @@ func (st *stateTransition) execute() (*ExecutionResult, error) {
|
|||
}
|
||||
|
||||
// Execute the transaction's call.
|
||||
ret, st.gasRemaining, vmerr = st.evm.Call(msg.From, st.to(), msg.Data, st.gasRemaining, value)
|
||||
ret, st.gasRemaining, vmerr = st.evm.Call(msg.From, st.to(), msg.Data, st.gasRemaining, &msg.Value)
|
||||
}
|
||||
|
||||
// Record the gas used excluding gas refunds. This value represents the actual
|
||||
|
|
@ -544,13 +576,13 @@ func (st *stateTransition) execute() (*ExecutionResult, error) {
|
|||
}
|
||||
st.returnGas()
|
||||
|
||||
effectiveTip := msg.GasPrice
|
||||
effectiveTipU256 := new(uint256.Int).Set(&msg.GasPrice)
|
||||
if rules.IsLondon {
|
||||
effectiveTip = new(big.Int).Sub(msg.GasPrice, st.evm.Context.BaseFee)
|
||||
baseFee := uint256.MustFromBig(st.evm.Context.BaseFee)
|
||||
effectiveTipU256.Sub(effectiveTipU256, baseFee)
|
||||
}
|
||||
effectiveTipU256, _ := uint256.FromBig(effectiveTip)
|
||||
|
||||
if st.evm.Config.NoBaseFee && msg.GasFeeCap.Sign() == 0 && msg.GasTipCap.Sign() == 0 {
|
||||
if st.evm.Config.NoBaseFee && msg.GasFeeCap.IsZero() && msg.GasTipCap.IsZero() {
|
||||
// Skip fee payment when NoBaseFee is set and the fee fields
|
||||
// are 0. This avoids a negative effectiveTip being applied to
|
||||
// the coinbase when simulating calls.
|
||||
|
|
@ -565,12 +597,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.
|
||||
|
|
@ -654,7 +686,7 @@ func (st *stateTransition) calcRefund() uint64 {
|
|||
// exchanged at the original rate.
|
||||
func (st *stateTransition) returnGas() {
|
||||
remaining := uint256.NewInt(st.gasRemaining)
|
||||
remaining.Mul(remaining, uint256.MustFromBig(st.msg.GasPrice))
|
||||
remaining.Mul(remaining, &st.msg.GasPrice)
|
||||
st.state.AddBalance(st.msg.From, remaining, tracing.BalanceIncreaseGasReturn)
|
||||
|
||||
if st.evm.Config.Tracer != nil && st.evm.Config.Tracer.OnGasChange != nil && st.gasRemaining > 0 {
|
||||
|
|
|
|||
|
|
@ -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 := arena.New[Stack](alloc)
|
||||
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
|
||||
|
||||
|
|
|
|||
|
|
@ -55,6 +55,7 @@ func (c Config) MarshalTOML() (interface{}, error) {
|
|||
EnablePreimageRecording bool
|
||||
EnableWitnessStats bool
|
||||
StatelessSelfValidation bool
|
||||
EnableArenaAlloc bool
|
||||
EnableStateSizeTracking bool
|
||||
VMTrace string
|
||||
VMTraceJsonConfig string
|
||||
|
|
@ -108,6 +109,7 @@ func (c Config) MarshalTOML() (interface{}, error) {
|
|||
enc.EnablePreimageRecording = c.EnablePreimageRecording
|
||||
enc.EnableWitnessStats = c.EnableWitnessStats
|
||||
enc.StatelessSelfValidation = c.StatelessSelfValidation
|
||||
enc.EnableArenaAlloc = c.EnableArenaAlloc
|
||||
enc.EnableStateSizeTracking = c.EnableStateSizeTracking
|
||||
enc.VMTrace = c.VMTrace
|
||||
enc.VMTraceJsonConfig = c.VMTraceJsonConfig
|
||||
|
|
@ -165,6 +167,7 @@ func (c *Config) UnmarshalTOML(unmarshal func(interface{}) error) error {
|
|||
EnablePreimageRecording *bool
|
||||
EnableWitnessStats *bool
|
||||
StatelessSelfValidation *bool
|
||||
EnableArenaAlloc *bool
|
||||
EnableStateSizeTracking *bool
|
||||
VMTrace *string
|
||||
VMTraceJsonConfig *string
|
||||
|
|
@ -297,6 +300,9 @@ func (c *Config) UnmarshalTOML(unmarshal func(interface{}) error) error {
|
|||
if dec.StatelessSelfValidation != nil {
|
||||
c.StatelessSelfValidation = *dec.StatelessSelfValidation
|
||||
}
|
||||
if dec.EnableArenaAlloc != nil {
|
||||
c.EnableArenaAlloc = *dec.EnableArenaAlloc
|
||||
}
|
||||
if dec.EnableStateSizeTracking != nil {
|
||||
c.EnableStateSizeTracking = *dec.EnableStateSizeTracking
|
||||
}
|
||||
|
|
|
|||
|
|
@ -81,10 +81,10 @@ func Estimate(ctx context.Context, call *core.Message, opts *Options, gasCap uin
|
|||
|
||||
// Normalize the max fee per gas the call is willing to spend.
|
||||
var feeCap *big.Int
|
||||
if call.GasFeeCap != nil {
|
||||
feeCap = call.GasFeeCap
|
||||
} else if call.GasPrice != nil {
|
||||
feeCap = call.GasPrice
|
||||
if !call.GasFeeCap.IsZero() {
|
||||
feeCap = call.GasFeeCap.ToBig()
|
||||
} else if !call.GasPrice.IsZero() {
|
||||
feeCap = call.GasPrice.ToBig()
|
||||
} else {
|
||||
feeCap = common.Big0
|
||||
}
|
||||
|
|
@ -93,17 +93,18 @@ func Estimate(ctx context.Context, call *core.Message, opts *Options, gasCap uin
|
|||
balance := opts.State.GetBalance(call.From).ToBig()
|
||||
|
||||
available := balance
|
||||
if call.Value != nil {
|
||||
if call.Value.Cmp(available) >= 0 {
|
||||
if !call.Value.IsZero() {
|
||||
valueBig := call.Value.ToBig()
|
||||
if valueBig.Cmp(available) >= 0 {
|
||||
return 0, nil, core.ErrInsufficientFundsForTransfer
|
||||
}
|
||||
available.Sub(available, call.Value)
|
||||
available.Sub(available, valueBig)
|
||||
}
|
||||
if opts.Config.IsCancun(opts.Header.Number, opts.Header.Time) && len(call.BlobHashes) > 0 {
|
||||
blobGasPerBlob := new(big.Int).SetInt64(params.BlobTxBlobGasPerBlob)
|
||||
blobBalanceUsage := new(big.Int).SetInt64(int64(len(call.BlobHashes)))
|
||||
blobBalanceUsage.Mul(blobBalanceUsage, blobGasPerBlob)
|
||||
blobBalanceUsage.Mul(blobBalanceUsage, call.BlobGasFeeCap)
|
||||
blobBalanceUsage.Mul(blobBalanceUsage, call.BlobGasFeeCap.ToBig())
|
||||
if blobBalanceUsage.Cmp(available) >= 0 {
|
||||
return 0, nil, core.ErrInsufficientFunds
|
||||
}
|
||||
|
|
@ -113,9 +114,9 @@ func Estimate(ctx context.Context, call *core.Message, opts *Options, gasCap uin
|
|||
|
||||
// If the allowance is larger than maximum uint64, skip checking
|
||||
if allowance.IsUint64() && hi > allowance.Uint64() {
|
||||
transfer := call.Value
|
||||
if transfer == nil {
|
||||
transfer = new(big.Int)
|
||||
transfer := new(big.Int)
|
||||
if !call.Value.IsZero() {
|
||||
transfer = call.Value.ToBig()
|
||||
}
|
||||
log.Debug("Gas estimation capped by limited funds", "original", hi, "balance", balance,
|
||||
"sent", transfer, "maxFeePerGas", feeCap, "fundable", allowance)
|
||||
|
|
@ -252,7 +253,7 @@ func run(ctx context.Context, call *core.Message, opts *Options) (*core.Executio
|
|||
if call.GasPrice.Sign() == 0 {
|
||||
evmContext.BaseFee = new(big.Int)
|
||||
}
|
||||
if call.BlobGasFeeCap != nil && call.BlobGasFeeCap.BitLen() == 0 {
|
||||
if call.BlobGasFeeCap.IsZero() {
|
||||
evmContext.BlobBaseFee = new(big.Int)
|
||||
}
|
||||
evm := vm.NewEVM(evmContext, dirtyState, opts.Config, vm.Config{NoBaseFee: true})
|
||||
|
|
|
|||
|
|
@ -994,7 +994,7 @@ func (api *API) TraceCall(ctx context.Context, args ethapi.TransactionArgs, bloc
|
|||
if msg.GasPrice.Sign() == 0 {
|
||||
blockContext.BaseFee = new(big.Int)
|
||||
}
|
||||
if msg.BlobGasFeeCap != nil && msg.BlobGasFeeCap.BitLen() == 0 {
|
||||
if !msg.BlobGasFeeCap.IsZero() && msg.BlobGasFeeCap.BitLen() == 0 {
|
||||
blockContext.BlobBaseFee = new(big.Int)
|
||||
}
|
||||
if config != nil {
|
||||
|
|
|
|||
|
|
@ -761,7 +761,7 @@ func applyMessage(ctx context.Context, b Backend, args TransactionArgs, state *s
|
|||
if msg.GasPrice.Sign() == 0 {
|
||||
blockContext.BaseFee = new(big.Int)
|
||||
}
|
||||
if msg.BlobGasFeeCap != nil && msg.BlobGasFeeCap.BitLen() == 0 {
|
||||
if !msg.BlobGasFeeCap.IsZero() && msg.BlobGasFeeCap.BitLen() == 0 {
|
||||
blockContext.BlobBaseFee = new(big.Int)
|
||||
}
|
||||
evm := b.GetEVM(ctx, state, header, vmConfig, blockContext)
|
||||
|
|
@ -1366,7 +1366,7 @@ func AccessList(ctx context.Context, b Backend, blockNrOrHash rpc.BlockNumberOrH
|
|||
if msg.GasPrice.Sign() == 0 {
|
||||
evm.Context.BaseFee = new(big.Int)
|
||||
}
|
||||
if msg.BlobGasFeeCap != nil && msg.BlobGasFeeCap.BitLen() == 0 {
|
||||
if !msg.BlobGasFeeCap.IsZero() && msg.BlobGasFeeCap.BitLen() == 0 {
|
||||
evm.Context.BlobBaseFee = new(big.Int)
|
||||
}
|
||||
res, err := core.ApplyMessage(evm, msg, new(core.GasPool).AddGas(msg.GasLimit))
|
||||
|
|
|
|||
|
|
@ -477,18 +477,25 @@ func (args *TransactionArgs) ToMessage(baseFee *big.Int, skipNonceCheck bool) *c
|
|||
if args.AccessList != nil {
|
||||
accessList = *args.AccessList
|
||||
}
|
||||
bigToU256 := func(b *big.Int) uint256.Int {
|
||||
if b == nil {
|
||||
return uint256.Int{}
|
||||
}
|
||||
v, _ := uint256.FromBig(b)
|
||||
return *v
|
||||
}
|
||||
return &core.Message{
|
||||
From: args.from(),
|
||||
To: args.To,
|
||||
Value: (*big.Int)(args.Value),
|
||||
Value: bigToU256((*big.Int)(args.Value)),
|
||||
Nonce: uint64(*args.Nonce),
|
||||
GasLimit: uint64(*args.Gas),
|
||||
GasPrice: gasPrice,
|
||||
GasFeeCap: gasFeeCap,
|
||||
GasTipCap: gasTipCap,
|
||||
GasPrice: bigToU256(gasPrice),
|
||||
GasFeeCap: bigToU256(gasFeeCap),
|
||||
GasTipCap: bigToU256(gasTipCap),
|
||||
Data: args.data(),
|
||||
AccessList: accessList,
|
||||
BlobGasFeeCap: (*big.Int)(args.BlobFeeCap),
|
||||
BlobGasFeeCap: bigToU256((*big.Int)(args.BlobFeeCap)),
|
||||
BlobHashes: args.BlobHashes,
|
||||
SetCodeAuthorizations: args.AuthorizationList,
|
||||
SkipNonceChecks: skipNonceCheck,
|
||||
|
|
|
|||
BIN
keeper-zisk.elf
Normal file
BIN
keeper-zisk.elf
Normal file
Binary file not shown.
|
|
@ -35,7 +35,6 @@ import (
|
|||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/core/vm"
|
||||
"github.com/ethereum/go-ethereum/eth/tracers/logger"
|
||||
"github.com/holiman/uint256"
|
||||
)
|
||||
|
||||
func initMatcher(st *testMatcher) {
|
||||
|
|
@ -316,7 +315,7 @@ func runBenchmark(b *testing.B, t *StateTest) {
|
|||
start := time.Now()
|
||||
|
||||
// Execute the message.
|
||||
_, leftOverGas, err := evm.Call(sender.Address(), *msg.To, msg.Data, msg.GasLimit, uint256.MustFromBig(msg.Value))
|
||||
_, leftOverGas, err := evm.Call(sender.Address(), *msg.To, msg.Data, msg.GasLimit, &msg.Value)
|
||||
if err != nil {
|
||||
b.Error(err)
|
||||
return
|
||||
|
|
|
|||
|
|
@ -477,19 +477,23 @@ func (tx *stTransaction) toMessage(ps stPostState, baseFee *big.Int) (*core.Mess
|
|||
}
|
||||
}
|
||||
|
||||
var blobGasFeeCap uint256.Int
|
||||
if tx.BlobGasFeeCap != nil {
|
||||
blobGasFeeCap = *uint256.MustFromBig(tx.BlobGasFeeCap)
|
||||
}
|
||||
msg := &core.Message{
|
||||
From: from,
|
||||
To: to,
|
||||
Nonce: tx.Nonce,
|
||||
Value: value,
|
||||
Value: *uint256.MustFromBig(value),
|
||||
GasLimit: gasLimit,
|
||||
GasPrice: gasPrice,
|
||||
GasFeeCap: tx.MaxFeePerGas,
|
||||
GasTipCap: tx.MaxPriorityFeePerGas,
|
||||
GasPrice: *uint256.MustFromBig(gasPrice),
|
||||
GasFeeCap: *uint256.MustFromBig(tx.MaxFeePerGas),
|
||||
GasTipCap: *uint256.MustFromBig(tx.MaxPriorityFeePerGas),
|
||||
Data: data,
|
||||
AccessList: accessList,
|
||||
BlobHashes: tx.BlobVersionedHashes,
|
||||
BlobGasFeeCap: tx.BlobGasFeeCap,
|
||||
BlobGasFeeCap: blobGasFeeCap,
|
||||
SetCodeAuthorizations: authList,
|
||||
}
|
||||
return msg, nil
|
||||
|
|
|
|||
Loading…
Reference in a new issue