This commit is contained in:
Emualliug Tellab 2026-03-03 21:54:30 -08:00 committed by GitHub
commit cc16e93193
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
28 changed files with 1320 additions and 135 deletions

View file

@ -141,6 +141,7 @@ var (
utils.VMTraceJsonConfigFlag,
utils.VMWitnessStatsFlag,
utils.VMStatelessSelfValidationFlag,
utils.VMArenaAllocFlag,
utils.NetworkIdFlag,
utils.EthStatsURLFlag,
utils.GpoBlocksFlag,

View file

@ -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 {

View file

@ -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
View 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]
}

View 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
View 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
View 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]
}

View 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)
}
}

View file

@ -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))

View file

@ -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

View file

@ -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: &params.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: &params.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))

View file

@ -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 {

View file

@ -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 {

View file

@ -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.

View file

@ -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,

View file

@ -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 {

View 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)
}
}

View file

@ -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)
}

View file

@ -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
}

View file

@ -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

View file

@ -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
}

View file

@ -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})

View file

@ -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 {

View file

@ -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))

View file

@ -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

Binary file not shown.

View file

@ -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

View file

@ -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