diff --git a/accounts/keystore/watch.go b/accounts/keystore/watch.go
index 1bef321cd1..e2fcd1871a 100644
--- a/accounts/keystore/watch.go
+++ b/accounts/keystore/watch.go
@@ -14,8 +14,8 @@
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see .
-//go:build (darwin && !ios && cgo) || freebsd || (linux && !arm64) || netbsd || solaris
-// +build darwin,!ios,cgo freebsd linux,!arm64 netbsd solaris
+//go:build (darwin && !ios && cgo) || freebsd || linux || netbsd || solaris
+// +build darwin,!ios,cgo freebsd linux netbsd solaris
package keystore
diff --git a/accounts/keystore/watch_fallback.go b/accounts/keystore/watch_fallback.go
index e3c133b3f6..ee1b989e63 100644
--- a/accounts/keystore/watch_fallback.go
+++ b/accounts/keystore/watch_fallback.go
@@ -14,8 +14,8 @@
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see .
-//go:build (darwin && !cgo) || ios || (linux && arm64) || windows || (!darwin && !freebsd && !linux && !netbsd && !solaris)
-// +build darwin,!cgo ios linux,arm64 windows !darwin,!freebsd,!linux,!netbsd,!solaris
+//go:build (darwin && !cgo) || ios || windows || (!darwin && !freebsd && !linux && !netbsd && !solaris)
+// +build darwin,!cgo ios windows !darwin,!freebsd,!linux,!netbsd,!solaris
// This is the fallback implementation of directory watching.
// It is used on unsupported platforms.
diff --git a/accounts/scwallet/hub.go b/accounts/scwallet/hub.go
index 1b1899dc8e..185815365e 100644
--- a/accounts/scwallet/hub.go
+++ b/accounts/scwallet/hub.go
@@ -113,7 +113,7 @@ func (hub *Hub) readPairings() error {
}
func (hub *Hub) writePairings() error {
- pairingFile, err := os.OpenFile(filepath.Join(hub.datadir, "smartcards.json"), os.O_RDWR|os.O_CREATE, 0755)
+ pairingFile, err := os.OpenFile(filepath.Join(hub.datadir, "smartcards.json"), os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0755)
if err != nil {
return err
}
@@ -129,11 +129,8 @@ func (hub *Hub) writePairings() error {
return err
}
- if _, err := pairingFile.Write(pairingData); err != nil {
- return err
- }
-
- return nil
+ _, err = pairingFile.Write(pairingData)
+ return err
}
func (hub *Hub) pairing(wallet *Wallet) *smartcardPairing {
diff --git a/cmd/evm/internal/t8ntool/execution.go b/cmd/evm/internal/t8ntool/execution.go
index f17829ec53..253ebe1111 100644
--- a/cmd/evm/internal/t8ntool/execution.go
+++ b/cmd/evm/internal/t8ntool/execution.go
@@ -17,9 +17,11 @@
package t8ntool
import (
+ "encoding/json"
"fmt"
stdmath "math"
"math/big"
+ "os"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
@@ -47,6 +49,9 @@ type Prestate struct {
Env stEnv `json:"env"`
Pre types.GenesisAlloc `json:"pre"`
TreeLeaves map[common.Hash]hexutil.Bytes `json:"vkt,omitempty"`
+ // AllocPath, when non-empty, causes Apply to stream the alloc from disk
+ // instead of reading Pre, so the full map never materializes in memory.
+ AllocPath string `json:"-"`
}
//go:generate go run github.com/fjl/gencodec -type ExecutionResult -field-override executionResultMarshaling -out gen_execresult.go
@@ -146,8 +151,19 @@ func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig,
return h
}
var (
- isEIP4762 = chainConfig.IsUBT(big.NewInt(int64(pre.Env.Number)), pre.Env.Timestamp)
- statedb = MakePreState(rawdb.NewMemoryDatabase(), pre.Pre, isEIP4762)
+ isEIP4762 = chainConfig.IsUBT(big.NewInt(int64(pre.Env.Number)), pre.Env.Timestamp)
+ statedb *state.StateDB
+ )
+ if pre.AllocPath != "" {
+ var err error
+ statedb, err = MakePreStateStreaming(rawdb.NewMemoryDatabase(), pre.AllocPath, isEIP4762)
+ if err != nil {
+ return nil, nil, nil, err
+ }
+ } else {
+ statedb = MakePreState(rawdb.NewMemoryDatabase(), pre.Pre, isEIP4762)
+ }
+ var (
signer = types.MakeSigner(chainConfig, new(big.Int).SetUint64(pre.Env.Number), pre.Env.Timestamp)
gaspool = core.NewGasPool(pre.Env.GasLimit)
blockHash = common.Hash{0x13, 0x37}
@@ -414,6 +430,76 @@ func MakePreState(db ethdb.Database, accounts types.GenesisAlloc, isBintrie bool
return statedb
}
+// MakePreStateStreaming is like MakePreState, but decodes the alloc from disk
+// one account at a time so the full map is never held in memory.
+func MakePreStateStreaming(db ethdb.Database, allocPath string, isBintrie bool) (*state.StateDB, error) {
+ tdb := triedb.NewDatabase(db, &triedb.Config{Preimages: true, IsUBT: isBintrie})
+ sdb := state.NewDatabase(tdb, nil)
+
+ root := types.EmptyRootHash
+ if isBintrie {
+ root = types.EmptyBinaryHash
+ }
+ statedb, err := state.New(root, sdb)
+ if err != nil {
+ return nil, NewError(ErrorEVM, fmt.Errorf("failed to create initial statedb: %v", err))
+ }
+
+ f, err := os.Open(allocPath)
+ if err != nil {
+ return nil, NewError(ErrorIO, fmt.Errorf("failed reading alloc file: %v", err))
+ }
+ defer f.Close()
+
+ dec := json.NewDecoder(f)
+ tok, err := dec.Token()
+ if err != nil {
+ return nil, NewError(ErrorJson, fmt.Errorf("failed reading alloc opening token: %v", err))
+ }
+ if d, ok := tok.(json.Delim); !ok || d != '{' {
+ return nil, NewError(ErrorJson, fmt.Errorf("expected alloc object, got %v", tok))
+ }
+ for dec.More() {
+ keyTok, err := dec.Token()
+ if err != nil {
+ return nil, NewError(ErrorJson, fmt.Errorf("failed reading alloc key: %v", err))
+ }
+ keyStr, ok := keyTok.(string)
+ if !ok {
+ return nil, NewError(ErrorJson, fmt.Errorf("alloc key not a string: %v", keyTok))
+ }
+ addr := common.HexToAddress(keyStr)
+ var acct types.Account
+ if err := dec.Decode(&acct); err != nil {
+ return nil, NewError(ErrorJson, fmt.Errorf("failed decoding account %s: %v", keyStr, err))
+ }
+ statedb.SetCode(addr, acct.Code, tracing.CodeChangeUnspecified)
+ statedb.SetNonce(addr, acct.Nonce, tracing.NonceChangeGenesis)
+ if acct.Balance != nil {
+ statedb.SetBalance(addr, uint256.MustFromBig(acct.Balance), tracing.BalanceIncreaseGenesisBalance)
+ }
+ for k, v := range acct.Storage {
+ statedb.SetState(addr, k, v)
+ }
+ }
+ if _, err := dec.Token(); err != nil {
+ return nil, NewError(ErrorJson, fmt.Errorf("failed reading alloc closing token: %v", err))
+ }
+
+ root, err = statedb.Commit(0, false, false)
+ if err != nil {
+ return nil, NewError(ErrorEVM, fmt.Errorf("failed to commit initial state: %v", err))
+ }
+ if isBintrie {
+ return statedb, nil
+ }
+ statedb, err = state.New(root, sdb)
+ if err != nil {
+ return nil, NewError(ErrorEVM, fmt.Errorf("failed to reopen state after commit: %v", err))
+ }
+ return statedb, nil
+}
+
func rlpHash(x any) (h common.Hash) {
hw := keccak.NewLegacyKeccak256()
rlp.Encode(hw, x)
diff --git a/cmd/evm/internal/t8ntool/transaction.go b/cmd/evm/internal/t8ntool/transaction.go
index ad89876601..7e068c06af 100644
--- a/cmd/evm/internal/t8ntool/transaction.go
+++ b/cmd/evm/internal/t8ntool/transaction.go
@@ -185,7 +185,9 @@ func Transaction(ctx *cli.Context) error {
}
}
- if chainConfig.IsOsaka(new(big.Int), 0) && tx.Gas() > params.MaxTxGas {
+ isOsaka := chainConfig.IsOsaka(new(big.Int), 0)
+ isAmsterdam := chainConfig.IsAmsterdam(new(big.Int), 0)
+ if isOsaka && !isAmsterdam && tx.Gas() > params.MaxTxGas {
r.Error = errors.New("gas limit exceeds maximum")
}
results = append(results, r)
diff --git a/cmd/evm/internal/t8ntool/transition.go b/cmd/evm/internal/t8ntool/transition.go
index 6a23e9dc70..e0bb3a449d 100644
--- a/cmd/evm/internal/t8ntool/transition.go
+++ b/cmd/evm/internal/t8ntool/transition.go
@@ -17,6 +17,7 @@
package t8ntool
import (
+ "bufio"
"encoding/json"
"errors"
"fmt"
@@ -115,11 +116,10 @@ func Transition(ctx *cli.Context) error {
}
}
if allocStr != stdinSelector {
- if err := readFile(allocStr, "alloc", &inputData.Alloc); err != nil {
- return err
- }
+ prestate.AllocPath = allocStr
+ } else {
+ prestate.Pre = inputData.Alloc
}
- prestate.Pre = inputData.Alloc
if btStr != stdinSelector && btStr != "" {
if err := readFile(btStr, "BT", &inputData.BT); err != nil {
@@ -223,22 +223,57 @@ func Transition(ctx *cli.Context) error {
return err
}
}
- // Dump the execution result
+ // Dump the execution result.
var (
- collector = make(Alloc)
+ collector Alloc
btleaves map[common.Hash]hexutil.Bytes
)
isBinary := chainConfig.IsUBT(big.NewInt(int64(prestate.Env.Number)), prestate.Env.Timestamp)
- if !isBinary {
+ allocOutput := ctx.String(OutputAllocFlag.Name)
+ switch {
+ case !isBinary && allocOutput != "" && allocOutput != "stdout" && allocOutput != "stderr":
+ // Stream directly to the output file to avoid materializing the
+ // whole post-state in memory. dispatchOutput is told to skip alloc
+ // by clearing the output name.
+ if err := writeStreamedAlloc(filepath.Join(baseDir, allocOutput), s); err != nil {
+ return err
+ }
+ allocOutput = ""
+ case !isBinary:
+ collector = make(Alloc)
s.DumpToCollector(collector, nil)
- } else {
+ default:
btleaves = make(map[common.Hash]hexutil.Bytes)
if err := s.DumpBinTrieLeaves(btleaves); err != nil {
return err
}
}
+ return dispatchOutput(ctx, baseDir, result, collector, allocOutput, body, btleaves)
+}
- return dispatchOutput(ctx, baseDir, result, collector, body, btleaves)
+// writeStreamedAlloc writes the post-state alloc to path one account at a
+// time, producing the same JSON shape as saveFile on an Alloc map.
+func writeStreamedAlloc(path string, s *state.StateDB) error {
+ f, err := os.Create(path)
+ if err != nil {
+ return NewError(ErrorIO, fmt.Errorf("failed creating alloc output file: %v", err))
+ }
+ bw := bufio.NewWriter(f)
+ sa := newStreamingAlloc(bw)
+ s.DumpToCollector(sa, nil)
+ if err := sa.Close(); err != nil {
+ f.Close()
+ return NewError(ErrorIO, fmt.Errorf("failed writing alloc output: %v", err))
+ }
+ if err := bw.Flush(); err != nil {
+ f.Close()
+ return NewError(ErrorIO, fmt.Errorf("failed flushing alloc output: %v", err))
+ }
+ if err := f.Close(); err != nil {
+ return NewError(ErrorIO, fmt.Errorf("failed closing alloc output file: %v", err))
+ }
+ log.Info("Wrote file", "file", path)
+ return nil
}
func applyLondonChecks(env *stEnv, chainConfig *params.ChainConfig) error {
@@ -327,6 +362,10 @@ func (g Alloc) OnAccount(addr *common.Address, dumpAccount state.DumpAccount) {
if addr == nil {
return
}
+ g[*addr] = dumpAccountToTypesAccount(dumpAccount)
+}
+
+func dumpAccountToTypesAccount(dumpAccount state.DumpAccount) types.Account {
balance, _ := new(big.Int).SetString(dumpAccount.Balance, 0)
var storage map[common.Hash]common.Hash
if dumpAccount.Storage != nil {
@@ -335,13 +374,64 @@ func (g Alloc) OnAccount(addr *common.Address, dumpAccount state.DumpAccount) {
storage[k] = common.HexToHash(v)
}
}
- genesisAccount := types.Account{
+ return types.Account{
Code: dumpAccount.Code,
Storage: storage,
Balance: balance,
Nonce: dumpAccount.Nonce,
}
- g[*addr] = genesisAccount
+}
+
+// streamingAlloc is a DumpCollector that writes each account to w as it is
+// visited, emitting a single JSON object keyed by address. Close must be
+// called to emit the closing brace.
+type streamingAlloc struct {
+ w io.Writer
+ wroteOne bool
+ err error
+}
+
+func newStreamingAlloc(w io.Writer) *streamingAlloc {
+ return &streamingAlloc{w: w}
+}
+
+func (s *streamingAlloc) write(b []byte) {
+ if s.err != nil {
+ return
+ }
+ _, s.err = s.w.Write(b)
+}
+
+func (s *streamingAlloc) OnRoot(common.Hash) {
+ s.write([]byte{'{'})
+}
+
+func (s *streamingAlloc) OnAccount(addr *common.Address, dumpAccount state.DumpAccount) {
+ if s.err != nil || addr == nil {
+ return
+ }
+ keyJSON, err := json.Marshal(*addr)
+ if err != nil {
+ s.err = err
+ return
+ }
+ valueJSON, err := json.Marshal(dumpAccountToTypesAccount(dumpAccount))
+ if err != nil {
+ s.err = err
+ return
+ }
+ if s.wroteOne {
+ s.write([]byte{','})
+ }
+ s.write(keyJSON)
+ s.write([]byte{':'})
+ s.write(valueJSON)
+ s.wroteOne = true
+}
+
+func (s *streamingAlloc) Close() error {
+ s.write([]byte{'}'})
+ return s.err
}
// saveFile marshals the object to the given file
@@ -359,8 +449,9 @@ func saveFile(baseDir, filename string, data interface{}) error {
}
// dispatchOutput writes the output data to either stderr or stdout, or to the specified
-// files
-func dispatchOutput(ctx *cli.Context, baseDir string, result *ExecutionResult, alloc Alloc, body hexutil.Bytes, bt map[common.Hash]hexutil.Bytes) error {
+// files. An empty allocOutput skips the alloc dispatch, which is used when the
+// alloc has already been streamed to disk by the caller.
+func dispatchOutput(ctx *cli.Context, baseDir string, result *ExecutionResult, alloc Alloc, allocOutput string, body hexutil.Bytes, bt map[common.Hash]hexutil.Bytes) error {
stdOutObject := make(map[string]interface{})
stdErrObject := make(map[string]interface{})
dispatch := func(baseDir, fName, name string, obj interface{}) error {
@@ -378,7 +469,7 @@ func dispatchOutput(ctx *cli.Context, baseDir string, result *ExecutionResult, a
}
return nil
}
- if err := dispatch(baseDir, ctx.String(OutputAllocFlag.Name), "alloc", alloc); err != nil {
+ if err := dispatch(baseDir, allocOutput, "alloc", alloc); err != nil {
return err
}
if err := dispatch(baseDir, ctx.String(OutputResultFlag.Name), "result", result); err != nil {
diff --git a/core/state/reader_eip_7928.go b/core/state/reader_eip_7928.go
new file mode 100644
index 0000000000..ff315ac5eb
--- /dev/null
+++ b/core/state/reader_eip_7928.go
@@ -0,0 +1,247 @@
+// 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 .
+
+package state
+
+import (
+ "sync"
+
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/core/types"
+ "github.com/ethereum/go-ethereum/core/types/bal"
+)
+
+// The EIP27928 reader utilizes a hierarchical architecture to optimize state
+// access during block execution:
+//
+// - Base layer: The reader is initialized with the pre-transition state root,
+// providing the access of the state.
+//
+// - Prefetching Layer: This base reader is wrapped by newPrefetchStateReader.
+// Using an Access List hint, it asynchronously fetches required state data
+// in the background, minimizing I/O blocking during transaction processing.
+//
+// - Execution Layer: To support parallel transaction execution within the EIP
+// 7928 context, readers are wrapped in ReaderWithBlockLevelAccessList.
+// This layer provides a "unified view" by merging the pre-transition state
+// with mutated states from preceding transactions in the block.
+//
+// - Tracking Layer: Finally, the readerTracker wraps the execution reader to
+// capture all state reads made during a specific transaction. These individual
+// reads are subsequently merged to construct a comprehensive access list
+// for the entire block.
+//
+// The architecture can be illustrated by the diagram below:
+//
+// ┌──────────────┴──────────────┐ ┌──────────────┴──────────────┐
+// │ ReaderWithBlockLevelAL │ │ ReaderWithBlockLevelAL │
+// │ (Pre-state + Mutations) │ │ (Pre-state + Mutations) │
+// └──────────────┬──────────────┘ └──────────────┬──────────────┘
+// │ │
+// └────────────────┬─────────────────┘
+// │
+// ┌──────────────┴──────────────┐
+// │ newPrefetchStateReader │ (Async I/O)
+// │ (Access List Hint driven) │
+// └──────────────┬──────────────┘
+// │
+// ┌──────────────┴──────────────┐
+// │ Base Reader │ (State Root)
+// │ (State & Contract Code) │
+// └─────────────────────────────┘
+
+// Note: The block producer, which is responsible for generating the block
+// along with the block-level access list, does not maintain the internal
+// hierarchy (e.g., PrefetchStateReader or ReaderWithBlockLevelAL).
+// Instead, it directly utilizes the readerTracker, wrapped around the
+// base reader, to construct the access list.
+
+type fetchTask struct {
+ addr common.Address
+ slots []common.Hash
+}
+
+func (t *fetchTask) weight() int { return 1 + len(t.slots) }
+
+type prefetchStateReader struct {
+ StateReader
+
+ tasks []*fetchTask
+ nThreads int
+ done chan struct{}
+ term chan struct{}
+ closeOnce sync.Once
+}
+
+// nolint:unused
+func newPrefetchStateReader(reader StateReader, accessList map[common.Address][]common.Hash, nThreads int) *prefetchStateReader {
+ tasks := make([]*fetchTask, 0, len(accessList))
+ for addr, slots := range accessList {
+ tasks = append(tasks, &fetchTask{
+ addr: addr,
+ slots: slots,
+ })
+ }
+ return newPrefetchStateReaderInternal(reader, tasks, nThreads)
+}
+
+func newPrefetchStateReaderInternal(reader StateReader, tasks []*fetchTask, nThreads int) *prefetchStateReader {
+ r := &prefetchStateReader{
+ StateReader: reader,
+ tasks: tasks,
+ nThreads: nThreads,
+ done: make(chan struct{}),
+ term: make(chan struct{}),
+ }
+ go r.prefetch()
+ return r
+}
+
+func (r *prefetchStateReader) Close() {
+ r.closeOnce.Do(func() {
+ close(r.term)
+ <-r.done
+ })
+}
+
+func (r *prefetchStateReader) Wait() error {
+ select {
+ case <-r.term:
+ return nil
+ case <-r.done:
+ return nil
+ }
+}
+
+func (r *prefetchStateReader) prefetch() {
+ defer close(r.done)
+
+ if len(r.tasks) == 0 {
+ return
+ }
+ var total int
+ for _, t := range r.tasks {
+ total += t.weight()
+ }
+ var (
+ wg sync.WaitGroup
+ unit = (total + r.nThreads - 1) / r.nThreads // round-up the per worker unit
+ )
+ for i := 0; i < r.nThreads; i++ {
+ start := i * unit
+ if start >= total {
+ break
+ }
+ limit := (i + 1) * unit
+ if i == r.nThreads-1 {
+ limit = total
+ }
+ // Schedule the worker for prefetching, the items on the range [start, limit)
+ // is exclusively assigned for this worker.
+ wg.Add(1)
+ go func(workerID, startW, endW int) {
+ r.process(startW, endW)
+ wg.Done()
+ }(i, start, limit)
+ }
+ wg.Wait()
+}
+
+func (r *prefetchStateReader) process(start, limit int) {
+ var total = 0
+ for _, t := range r.tasks {
+ tw := t.weight()
+ if total+tw > start {
+ s := 0
+ if start > total {
+ s = start - total
+ }
+ l := tw
+ if limit < total+tw {
+ l = limit - total
+ }
+ for j := s; j < l; j++ {
+ select {
+ case <-r.term:
+ return
+ default:
+ if j == 0 {
+ r.StateReader.Account(t.addr)
+ } else {
+ r.StateReader.Storage(t.addr, t.slots[j-1])
+ }
+ }
+ }
+ }
+ total += tw
+ if total >= limit {
+ return
+ }
+ }
+}
+
+// ReaderWithBlockLevelAccessList provides state access that reflects the
+// pre-transition state combined with the mutations made by transactions
+// prior to TxIndex.
+type ReaderWithBlockLevelAccessList struct {
+ Reader
+ AccessList *bal.ConstructionBlockAccessList
+ TxIndex int
+}
+
+// NewReaderWithBlockLevelAccessList constructs a reader for accessing states
+// with the mutations made by transactions prior to txIndex.
+//
+// The txIndex refers to the call frame as such:
+// - 0 for pre‑execution system contract calls.
+// - 1 … n for transactions (in block order).
+// - n + 1 for post‑execution system contract calls.
+func NewReaderWithBlockLevelAccessList(base Reader, accessList *bal.ConstructionBlockAccessList, txIndex int) *ReaderWithBlockLevelAccessList {
+ return &ReaderWithBlockLevelAccessList{
+ Reader: base,
+ AccessList: accessList,
+ TxIndex: txIndex,
+ }
+}
+
+// Account implements Reader, returning the account with the specific address.
+func (r *ReaderWithBlockLevelAccessList) Account(addr common.Address) (*types.StateAccount, error) {
+ panic("implement me")
+}
+
+// Storage implements Reader, returning the storage slot with the specific
+// address and slot key.
+func (r *ReaderWithBlockLevelAccessList) Storage(addr common.Address, slot common.Hash) (common.Hash, error) {
+ panic("implement me")
+}
+
+// Has implements Reader, returning the flag indicating whether the contract
+// code with specified address and hash exists or not.
+func (r *ReaderWithBlockLevelAccessList) Has(addr common.Address, codeHash common.Hash) bool {
+ panic("implement me")
+}
+
+// Code implements Reader, returning the contract code with specified address
+// and hash.
+func (r *ReaderWithBlockLevelAccessList) Code(addr common.Address, codeHash common.Hash) ([]byte, error) {
+ panic("implement me")
+}
+
+// CodeSize implements Reader, returning the contract code size with specified
+// address and hash.
+func (r *ReaderWithBlockLevelAccessList) CodeSize(addr common.Address, codeHash common.Hash) (int, error) {
+ panic("implement me")
+}
diff --git a/core/state/reader_eip_7928_test.go b/core/state/reader_eip_7928_test.go
new file mode 100644
index 0000000000..b2d432258c
--- /dev/null
+++ b/core/state/reader_eip_7928_test.go
@@ -0,0 +1,145 @@
+// 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 .
+
+package state
+
+import (
+ "fmt"
+ "math/rand"
+ "sync"
+ "testing"
+
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/core/types"
+ "github.com/ethereum/go-ethereum/internal/testrand"
+)
+
+type countingStateReader struct {
+ accounts map[common.Address]int
+ storages map[common.Address]map[common.Hash]int
+ lock sync.Mutex
+}
+
+func newRefStateReader() *countingStateReader {
+ return &countingStateReader{
+ accounts: make(map[common.Address]int),
+ storages: make(map[common.Address]map[common.Hash]int),
+ }
+}
+
+func (r *countingStateReader) validate(total int) error {
+ var sum int
+ for addr, n := range r.accounts {
+ if n != 1 {
+ return fmt.Errorf("duplicated account access: %x-%d", addr, n)
+ }
+ sum += 1
+
+ slots, exists := r.storages[addr]
+ if !exists {
+ continue
+ }
+ for key, n := range slots {
+ if n != 1 {
+ return fmt.Errorf("duplicated storage access: %x-%x-%d", addr, key, n)
+ }
+ sum += 1
+ }
+ }
+ for addr := range r.storages {
+ _, exists := r.accounts[addr]
+ if !exists {
+ return fmt.Errorf("dangling storage access: %x", addr)
+ }
+ }
+ if sum != total {
+ return fmt.Errorf("unexpected number of access, want: %d, got: %d", total, sum)
+ }
+ return nil
+}
+
+func (r *countingStateReader) Account(addr common.Address) (*types.StateAccount, error) {
+ r.lock.Lock()
+ defer r.lock.Unlock()
+
+ r.accounts[addr] += 1
+ return nil, nil
+}
+func (r *countingStateReader) Storage(addr common.Address, slot common.Hash) (common.Hash, error) {
+ r.lock.Lock()
+ defer r.lock.Unlock()
+
+ slots, exists := r.storages[addr]
+ if !exists {
+ slots = make(map[common.Hash]int)
+ r.storages[addr] = slots
+ }
+ slots[slot] += 1
+ return common.Hash{}, nil
+}
+
+func makeFetchTasks(n int) ([]*fetchTask, int) {
+ var (
+ total int
+ tasks []*fetchTask
+ )
+ for i := 0; i < n; i++ {
+ var slots []common.Hash
+ if rand.Intn(3) != 0 {
+ for j := 0; j < rand.Intn(100); j++ {
+ slots = append(slots, testrand.Hash())
+ }
+ }
+ tasks = append(tasks, &fetchTask{
+ addr: testrand.Address(),
+ slots: slots,
+ })
+ total += len(slots) + 1
+ }
+ return tasks, total
+}
+
+func TestPrefetchReader(t *testing.T) {
+ type suite struct {
+ tasks []*fetchTask
+ threads int
+ total int
+ }
+ var suites []suite
+ for i := 0; i < 100; i++ {
+ tasks, total := makeFetchTasks(100)
+ suites = append(suites, suite{
+ tasks: tasks,
+ threads: rand.Intn(30) + 1,
+ total: total,
+ })
+ }
+ // num(tasks) < num(threads)
+ tasks, total := makeFetchTasks(1)
+ suites = append(suites, suite{
+ tasks: tasks,
+ threads: 100,
+ total: total,
+ })
+ for _, s := range suites {
+ r := newRefStateReader()
+ pr := newPrefetchStateReaderInternal(r, s.tasks, s.threads)
+ pr.Wait()
+ if err := r.validate(s.total); err != nil {
+ t.Fatal(err)
+ }
+ }
+}
diff --git a/core/state/state_object.go b/core/state/state_object.go
index 264dfd920d..8e72486825 100644
--- a/core/state/state_object.go
+++ b/core/state/state_object.go
@@ -162,8 +162,11 @@ func (s *stateObject) getPrefetchedTrie() Trie {
// GetState retrieves a value associated with the given storage key.
func (s *stateObject) GetState(key common.Hash) common.Hash {
- value, _ := s.getState(key)
- return value
+ value, dirty := s.dirtyStorage[key]
+ if dirty {
+ return value
+ }
+ return s.GetCommittedState(key)
}
// getState retrieves a value associated with the given storage key, along with
diff --git a/core/state_prefetcher.go b/core/state_prefetcher.go
index c91d40d94f..ed292d0beb 100644
--- a/core/state_prefetcher.go
+++ b/core/state_prefetcher.go
@@ -93,6 +93,7 @@ func (p *statePrefetcher) Prefetch(block *types.Block, statedb *state.StateDB, c
}
// Execute the message to preload the implicit touched states
evm := vm.NewEVM(NewEVMBlockContext(header, p.chain, nil), stateCpy, p.config, cfg)
+ defer evm.Release()
// Convert the transaction into an executable message and pre-cache its sender
msg, err := TransactionToMessage(tx, signer, header.BaseFee)
diff --git a/core/state_processor.go b/core/state_processor.go
index fda3bf8fe7..54ebbd047b 100644
--- a/core/state_processor.go
+++ b/core/state_processor.go
@@ -88,6 +88,7 @@ func (p *StateProcessor) Process(ctx context.Context, block *types.Block, stated
// Apply pre-execution system calls.
context = NewEVMBlockContext(header, p.chain, nil)
evm := vm.NewEVM(context, tracingStateDB, config, cfg)
+ defer evm.Release()
if beaconRoot := block.BeaconRoot(); beaconRoot != nil {
ProcessBeaconBlockRoot(*beaconRoot, evm)
diff --git a/core/state_transition.go b/core/state_transition.go
index c3ebffd060..c7b0593857 100644
--- a/core/state_transition.go
+++ b/core/state_transition.go
@@ -344,9 +344,10 @@ func (st *stateTransition) preCheck() error {
}
}
isOsaka := st.evm.ChainConfig().IsOsaka(st.evm.Context.BlockNumber, st.evm.Context.Time)
+ isAmsterdam := st.evm.ChainConfig().IsAmsterdam(st.evm.Context.BlockNumber, st.evm.Context.Time)
if !msg.SkipTransactionChecks {
// Verify tx gas limit does not exceed EIP-7825 cap.
- if isOsaka && msg.GasLimit > params.MaxTxGas {
+ if isOsaka && !isAmsterdam && msg.GasLimit > params.MaxTxGas {
return fmt.Errorf("%w (cap: %d, tx: %d)", ErrGasLimitTooHigh, params.MaxTxGas, msg.GasLimit)
}
// Make sure the sender is an EOA
diff --git a/core/txpool/legacypool/legacypool.go b/core/txpool/legacypool/legacypool.go
index 78a0161c41..00630de04c 100644
--- a/core/txpool/legacypool/legacypool.go
+++ b/core/txpool/legacypool/legacypool.go
@@ -1222,8 +1222,10 @@ func (pool *LegacyPool) runReorg(done chan struct{}, reset *txpoolResetRequest,
pool.mu.Lock()
if reset != nil {
if reset.newHead != nil && reset.oldHead != nil {
- // Discard the transactions with the gas limit higher than the cap.
- if pool.chainconfig.IsOsaka(reset.newHead.Number, reset.newHead.Time) && !pool.chainconfig.IsOsaka(reset.oldHead.Number, reset.oldHead.Time) {
+ // Discard the transactions with the gas limit higher than the cap at the
+ // Osaka fork boundary.
+ if pool.chainconfig.IsOsaka(reset.newHead.Number, reset.newHead.Time) &&
+ !pool.chainconfig.IsOsaka(reset.oldHead.Number, reset.oldHead.Time) {
var hashes []common.Hash
pool.all.Range(func(hash common.Hash, tx *types.Transaction) bool {
if tx.Gas() > params.MaxTxGas {
diff --git a/core/txpool/validation.go b/core/txpool/validation.go
index 85bf65ac40..6891dc94d2 100644
--- a/core/txpool/validation.go
+++ b/core/txpool/validation.go
@@ -92,7 +92,7 @@ func ValidateTransaction(tx *types.Transaction, head *types.Header, signer types
return err
}
}
- if rules.IsOsaka && tx.Gas() > params.MaxTxGas {
+ if rules.IsOsaka && !rules.IsAmsterdam && tx.Gas() > params.MaxTxGas {
return fmt.Errorf("%w (cap: %d, tx: %d)", core.ErrGasLimitTooHigh, params.MaxTxGas, tx.Gas())
}
// Transactions can't be negative. This may never happen using RLP decoded
diff --git a/core/vm/eips.go b/core/vm/eips.go
index 54e5cb0c60..33af8fd4fd 100644
--- a/core/vm/eips.go
+++ b/core/vm/eips.go
@@ -24,7 +24,6 @@ import (
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/tracing"
"github.com/ethereum/go-ethereum/params"
- "github.com/holiman/uint256"
)
var activators = map[int]func(*JumpTable){
@@ -92,8 +91,7 @@ func enable1884(jt *JumpTable) {
}
func opSelfBalance(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
- balance := evm.StateDB.GetBalance(scope.Contract.Address())
- scope.Stack.push(balance)
+ scope.Stack.get().Set(evm.StateDB.GetBalance(scope.Contract.Address()))
return nil, nil
}
@@ -111,8 +109,7 @@ func enable1344(jt *JumpTable) {
// opChainID implements CHAINID opcode
func opChainID(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
- chainId, _ := uint256.FromBig(evm.chainConfig.ChainID)
- scope.Stack.push(chainId)
+ scope.Stack.get().SetFromBig(evm.chainConfig.ChainID)
return nil, nil
}
@@ -222,8 +219,7 @@ func opTstore(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
// opBaseFee implements BASEFEE opcode
func opBaseFee(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
- baseFee, _ := uint256.FromBig(evm.Context.BaseFee)
- scope.Stack.push(baseFee)
+ scope.Stack.get().SetFromBig(evm.Context.BaseFee)
return nil, nil
}
@@ -240,7 +236,7 @@ func enable3855(jt *JumpTable) {
// opPush0 implements the PUSH0 opcode
func opPush0(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
- scope.Stack.push(new(uint256.Int))
+ scope.Stack.get().Clear()
return nil, nil
}
@@ -291,8 +287,7 @@ func opBlobHash(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
// opBlobBaseFee implements BLOBBASEFEE opcode
func opBlobBaseFee(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
- blobBaseFee, _ := uint256.FromBig(evm.Context.BlobBaseFee)
- scope.Stack.push(blobBaseFee)
+ scope.Stack.get().SetFromBig(evm.Context.BlobBaseFee)
return nil, nil
}
@@ -397,11 +392,11 @@ func opExtCodeCopyEIP4762(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, er
func opPush1EIP4762(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
var (
codeLen = uint64(len(scope.Contract.Code))
- integer = new(uint256.Int)
+ elem = scope.Stack.get()
)
*pc += 1
if *pc < codeLen {
- scope.Stack.push(integer.SetUint64(uint64(scope.Contract.Code[*pc])))
+ elem.SetUint64(uint64(scope.Contract.Code[*pc]))
if !scope.Contract.IsDeployment && !scope.Contract.IsSystemCall && *pc%31 == 0 {
// touch next chunk if PUSH1 is at the boundary. if so, *pc has
@@ -414,7 +409,7 @@ func opPush1EIP4762(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
}
}
} else {
- scope.Stack.push(integer.Clear())
+ elem.Clear()
}
return nil, nil
}
@@ -426,12 +421,11 @@ func makePushEIP4762(size uint64, pushByteSize int) executionFunc {
start = min(codeLen, int(*pc+1))
end = min(codeLen, start+pushByteSize)
)
- scope.Stack.push(new(uint256.Int).SetBytes(
+ scope.Stack.get().SetBytes(
common.RightPadBytes(
scope.Contract.Code[start:end],
pushByteSize,
- )),
- )
+ ))
if !scope.Contract.IsDeployment && !scope.Contract.IsSystemCall {
contractAddr := scope.Contract.Address()
@@ -583,7 +577,7 @@ func enable7702(jt *JumpTable) {
// opSlotNum enables the SLOTNUM opcode
func opSlotNum(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
- scope.Stack.push(uint256.NewInt(evm.Context.SlotNum))
+ scope.Stack.get().SetUint64(evm.Context.SlotNum)
return nil, nil
}
diff --git a/core/vm/evm.go b/core/vm/evm.go
index 579c81ed0a..c0a649cf1d 100644
--- a/core/vm/evm.go
+++ b/core/vm/evm.go
@@ -127,6 +127,8 @@ type EVM struct {
readOnly bool // Whether to throw on stateful modifications
returnData []byte // Last CALL's return data for subsequent reuse
+
+ arena *stackArena
}
// NewEVM constructs an EVM instance with the supplied block context, state
@@ -141,6 +143,7 @@ func NewEVM(blockCtx BlockContext, statedb StateDB, chainConfig *params.ChainCon
chainConfig: chainConfig,
chainRules: chainConfig.Rules(blockCtx.BlockNumber, blockCtx.Random != nil, blockCtx.Time),
jumpDests: newGlobalJumpDests(),
+ arena: newArena(),
}
evm.precompiles = activePrecompiledContracts(evm.chainRules)
@@ -223,6 +226,12 @@ func (evm *EVM) Cancel() {
evm.abort.Store(true)
}
+// Release returns some memory allocated by the EVM, should be called after the EVM was used
+// for the last time. Not necessary, but an improvement.
+func (evm *EVM) Release() {
+ returnStack(evm.arena)
+}
+
// Cancelled returns true if Cancel has been called
func (evm *EVM) Cancelled() bool {
return evm.abort.Load()
diff --git a/core/vm/gas_table.go b/core/vm/gas_table.go
index b3259b2ec7..046311f9cc 100644
--- a/core/vm/gas_table.go
+++ b/core/vm/gas_table.go
@@ -71,7 +71,7 @@ func memoryCopierGas(stackpos int) gasFunc {
return GasCosts{}, err
}
// And gas for copying data, charged per word at param.CopyGas
- words, overflow := stack.Back(stackpos).Uint64WithOverflow()
+ words, overflow := stack.back(stackpos).Uint64WithOverflow()
if overflow {
return GasCosts{}, ErrGasUintOverflow
}
@@ -100,7 +100,7 @@ func gasSStore(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySi
return GasCosts{}, ErrWriteProtection
}
var (
- y, x = stack.Back(1), stack.Back(0)
+ y, x = stack.back(1), stack.back(0)
current, original = evm.StateDB.GetStateAndCommittedState(contract.Address(), x.Bytes32())
)
// The legacy gas metering only takes into consideration the current state
@@ -192,7 +192,7 @@ func gasSStoreEIP2200(evm *EVM, contract *Contract, stack *Stack, mem *Memory, m
}
// Gas sentry honoured, do the actual gas calculation based on the stored value
var (
- y, x = stack.Back(1), stack.Back(0)
+ y, x = stack.back(1), stack.back(0)
current, original = evm.StateDB.GetStateAndCommittedState(contract.Address(), x.Bytes32())
)
value := common.Hash(y.Bytes32())
@@ -228,7 +228,7 @@ func gasSStoreEIP2200(evm *EVM, contract *Contract, stack *Stack, mem *Memory, m
func makeGasLog(n uint64) gasFunc {
return func(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (GasCosts, error) {
- requestedSize, overflow := stack.Back(1).Uint64WithOverflow()
+ requestedSize, overflow := stack.back(1).Uint64WithOverflow()
if overflow {
return GasCosts{}, ErrGasUintOverflow
}
@@ -261,7 +261,7 @@ func gasKeccak256(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memor
if err != nil {
return GasCosts{}, err
}
- wordGas, overflow := stack.Back(1).Uint64WithOverflow()
+ wordGas, overflow := stack.back(1).Uint64WithOverflow()
if overflow {
return GasCosts{}, ErrGasUintOverflow
}
@@ -299,7 +299,7 @@ func gasCreate2(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memoryS
if err != nil {
return GasCosts{}, err
}
- wordGas, overflow := stack.Back(2).Uint64WithOverflow()
+ wordGas, overflow := stack.back(2).Uint64WithOverflow()
if overflow {
return GasCosts{}, ErrGasUintOverflow
}
@@ -317,7 +317,7 @@ func gasCreateEip3860(evm *EVM, contract *Contract, stack *Stack, mem *Memory, m
if err != nil {
return GasCosts{}, err
}
- size, overflow := stack.Back(2).Uint64WithOverflow()
+ size, overflow := stack.back(2).Uint64WithOverflow()
if overflow {
return GasCosts{}, ErrGasUintOverflow
}
@@ -336,7 +336,7 @@ func gasCreate2Eip3860(evm *EVM, contract *Contract, stack *Stack, mem *Memory,
if err != nil {
return GasCosts{}, err
}
- size, overflow := stack.Back(2).Uint64WithOverflow()
+ size, overflow := stack.back(2).Uint64WithOverflow()
if overflow {
return GasCosts{}, ErrGasUintOverflow
}
@@ -352,7 +352,7 @@ func gasCreate2Eip3860(evm *EVM, contract *Contract, stack *Stack, mem *Memory,
}
func gasExpFrontier(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (GasCosts, error) {
- expByteLen := uint64((stack.data[stack.len()-2].BitLen() + 7) / 8)
+ expByteLen := uint64((stack.back(1).BitLen() + 7) / 8)
var (
gas = expByteLen * params.ExpByteFrontier // no overflow check required. Max is 256 * ExpByte gas
@@ -365,7 +365,7 @@ func gasExpFrontier(evm *EVM, contract *Contract, stack *Stack, mem *Memory, mem
}
func gasExpEIP158(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (GasCosts, error) {
- expByteLen := uint64((stack.data[stack.len()-2].BitLen() + 7) / 8)
+ expByteLen := uint64((stack.back(1).BitLen() + 7) / 8)
var (
gas = expByteLen * params.ExpByteEIP158 // no overflow check required. Max is 256 * ExpByte gas
@@ -390,7 +390,7 @@ func makeCallVariantGasCost(intrinsicFunc gasFunc) gasFunc {
if err != nil {
return GasCosts{}, err
}
- evm.callGasTemp, err = callGas(evm.chainRules.IsEIP150, contract.Gas.RegularGas, intrinsic.RegularGas, stack.Back(0))
+ evm.callGasTemp, err = callGas(evm.chainRules.IsEIP150, contract.Gas.RegularGas, intrinsic.RegularGas, stack.back(0))
if err != nil {
return GasCosts{}, err
}
@@ -405,8 +405,8 @@ func makeCallVariantGasCost(intrinsicFunc gasFunc) gasFunc {
func gasCallIntrinsic(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (GasCosts, error) {
var (
gas uint64
- transfersValue = !stack.Back(2).IsZero()
- address = common.Address(stack.Back(1).Bytes20())
+ transfersValue = !stack.back(2).IsZero()
+ address = common.Address(stack.back(1).Bytes20())
)
if evm.readOnly && transfersValue {
return GasCosts{}, ErrWriteProtection
@@ -453,7 +453,7 @@ func gasCallCodeIntrinsic(evm *EVM, contract *Contract, stack *Stack, mem *Memor
gas uint64
overflow bool
)
- if stack.Back(2).Sign() != 0 && !evm.chainRules.IsEIP4762 {
+ if stack.back(2).Sign() != 0 && !evm.chainRules.IsEIP4762 {
gas += params.CallValueTransferGas
}
if gas, overflow = math.SafeAdd(gas, memoryGas); overflow {
@@ -487,7 +487,7 @@ func gasSelfdestruct(evm *EVM, contract *Contract, stack *Stack, mem *Memory, me
// EIP150 homestead gas reprice fork:
if evm.chainRules.IsEIP150 {
gas = params.SelfdestructGasEIP150
- var address = common.Address(stack.Back(0).Bytes20())
+ var address = common.Address(stack.back(0).Bytes20())
if evm.chainRules.IsEIP158 {
// if empty and transfers value
diff --git a/core/vm/instructions.go b/core/vm/instructions.go
index 3311af0d22..4b05092cc7 100644
--- a/core/vm/instructions.go
+++ b/core/vm/instructions.go
@@ -24,7 +24,6 @@ import (
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/params"
- "github.com/holiman/uint256"
)
func opAdd(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
@@ -244,7 +243,7 @@ func opKeccak256(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
}
func opAddress(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
- scope.Stack.push(new(uint256.Int).SetBytes(scope.Contract.Address().Bytes()))
+ scope.Stack.get().SetBytes(scope.Contract.Address().Bytes())
return nil, nil
}
@@ -256,17 +255,17 @@ func opBalance(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
}
func opOrigin(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
- scope.Stack.push(new(uint256.Int).SetBytes(evm.Origin.Bytes()))
+ scope.Stack.get().SetBytes(evm.Origin.Bytes())
return nil, nil
}
func opCaller(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
- scope.Stack.push(new(uint256.Int).SetBytes(scope.Contract.Caller().Bytes()))
+ scope.Stack.get().SetBytes(scope.Contract.Caller().Bytes())
return nil, nil
}
func opCallValue(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
- scope.Stack.push(scope.Contract.value)
+ scope.Stack.get().Set(scope.Contract.value)
return nil, nil
}
@@ -282,7 +281,7 @@ func opCallDataLoad(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
}
func opCallDataSize(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
- scope.Stack.push(new(uint256.Int).SetUint64(uint64(len(scope.Contract.Input))))
+ scope.Stack.get().SetUint64(uint64(len(scope.Contract.Input)))
return nil, nil
}
@@ -305,7 +304,7 @@ func opCallDataCopy(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
}
func opReturnDataSize(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
- scope.Stack.push(new(uint256.Int).SetUint64(uint64(len(evm.returnData))))
+ scope.Stack.get().SetUint64(uint64(len(evm.returnData)))
return nil, nil
}
@@ -338,7 +337,7 @@ func opExtCodeSize(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
}
func opCodeSize(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
- scope.Stack.push(new(uint256.Int).SetUint64(uint64(len(scope.Contract.Code))))
+ scope.Stack.get().SetUint64(uint64(len(scope.Contract.Code)))
return nil, nil
}
@@ -416,7 +415,7 @@ func opExtCodeHash(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
}
func opGasprice(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
- scope.Stack.push(evm.GasPrice.Clone())
+ scope.Stack.get().Set(evm.GasPrice)
return nil, nil
}
@@ -451,35 +450,32 @@ func opBlockhash(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
}
func opCoinbase(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
- scope.Stack.push(new(uint256.Int).SetBytes(evm.Context.Coinbase.Bytes()))
+ scope.Stack.get().SetBytes(evm.Context.Coinbase.Bytes())
return nil, nil
}
func opTimestamp(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
- scope.Stack.push(new(uint256.Int).SetUint64(evm.Context.Time))
+ scope.Stack.get().SetUint64(evm.Context.Time)
return nil, nil
}
func opNumber(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
- v, _ := uint256.FromBig(evm.Context.BlockNumber)
- scope.Stack.push(v)
+ scope.Stack.get().SetFromBig(evm.Context.BlockNumber)
return nil, nil
}
func opDifficulty(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
- v, _ := uint256.FromBig(evm.Context.Difficulty)
- scope.Stack.push(v)
+ scope.Stack.get().SetFromBig(evm.Context.Difficulty)
return nil, nil
}
func opRandom(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
- v := new(uint256.Int).SetBytes(evm.Context.Random.Bytes())
- scope.Stack.push(v)
+ scope.Stack.get().SetBytes(evm.Context.Random.Bytes())
return nil, nil
}
func opGasLimit(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
- scope.Stack.push(new(uint256.Int).SetUint64(evm.Context.GasLimit))
+ scope.Stack.get().SetUint64(evm.Context.GasLimit)
return nil, nil
}
@@ -556,17 +552,17 @@ func opJumpdest(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
}
func opPc(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
- scope.Stack.push(new(uint256.Int).SetUint64(*pc))
+ scope.Stack.get().SetUint64(*pc)
return nil, nil
}
func opMsize(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
- scope.Stack.push(new(uint256.Int).SetUint64(uint64(scope.Memory.Len())))
+ scope.Stack.get().SetUint64(uint64(scope.Memory.Len()))
return nil, nil
}
func opGas(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
- scope.Stack.push(new(uint256.Int).SetUint64(scope.Contract.Gas.RegularGas))
+ scope.Stack.get().SetUint64(scope.Contract.Gas.RegularGas)
return nil, nil
}
@@ -1007,7 +1003,7 @@ func opDupN(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
}
//The n‘th stack item is duplicated at the top of the stack.
- scope.Stack.push(scope.Stack.Back(n - 1))
+ scope.Stack.push(scope.Stack.back(n - 1))
*pc += 1
return nil, nil
}
@@ -1034,10 +1030,10 @@ func opSwapN(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
return nil, &ErrStackUnderflow{stackLen: scope.Stack.len(), required: n + 1}
}
- // The (n+1)‘th stack item is swapped with the top of the stack.
- indexTop := scope.Stack.len() - 1
- indexN := scope.Stack.len() - 1 - n
- scope.Stack.data[indexTop], scope.Stack.data[indexN] = scope.Stack.data[indexN], scope.Stack.data[indexTop]
+ // The (n+1)’th stack item is swapped with the top of the stack.
+ top := scope.Stack.peek()
+ nth := scope.Stack.back(n)
+ *top, *nth = *nth, *top
*pc += 1
return nil, nil
}
@@ -1067,10 +1063,10 @@ func opExchange(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
return nil, &ErrStackUnderflow{stackLen: scope.Stack.len(), required: need}
}
- // The (n+1)‘th stack item is swapped with the (m+1)‘th stack item.
- indexN := scope.Stack.len() - 1 - n
- indexM := scope.Stack.len() - 1 - m
- scope.Stack.data[indexN], scope.Stack.data[indexM] = scope.Stack.data[indexM], scope.Stack.data[indexN]
+ // The (n+1)’th stack item is swapped with the (m+1)’th stack item.
+ nth := scope.Stack.back(n)
+ mth := scope.Stack.back(m)
+ *nth, *mth = *mth, *nth
*pc += 1
return nil, nil
}
@@ -1106,13 +1102,13 @@ func makeLog(size int) executionFunc {
func opPush1(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
var (
codeLen = uint64(len(scope.Contract.Code))
- integer = new(uint256.Int)
+ elem = scope.Stack.get()
)
*pc += 1
if *pc < codeLen {
- scope.Stack.push(integer.SetUint64(uint64(scope.Contract.Code[*pc])))
+ elem.SetUint64(uint64(scope.Contract.Code[*pc]))
} else {
- scope.Stack.push(integer.Clear())
+ elem.Clear()
}
return nil, nil
}
@@ -1121,14 +1117,14 @@ func opPush1(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
func opPush2(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
var (
codeLen = uint64(len(scope.Contract.Code))
- integer = new(uint256.Int)
+ elem = scope.Stack.get()
)
if *pc+2 < codeLen {
- scope.Stack.push(integer.SetBytes2(scope.Contract.Code[*pc+1 : *pc+3]))
+ elem.SetBytes2(scope.Contract.Code[*pc+1 : *pc+3])
} else if *pc+1 < codeLen {
- scope.Stack.push(integer.SetUint64(uint64(scope.Contract.Code[*pc+1]) << 8))
+ elem.SetUint64(uint64(scope.Contract.Code[*pc+1]) << 8)
} else {
- scope.Stack.push(integer.Clear())
+ elem.Clear()
}
*pc += 2
return nil, nil
@@ -1142,13 +1138,13 @@ func makePush(size uint64, pushByteSize int) executionFunc {
start = min(codeLen, int(*pc+1))
end = min(codeLen, start+pushByteSize)
)
- a := new(uint256.Int).SetBytes(scope.Contract.Code[start:end])
+ a := scope.Stack.get()
+ a.SetBytes(scope.Contract.Code[start:end])
// Missing bytes: pushByteSize - len(pushData)
if missing := pushByteSize - (end - start); missing > 0 {
a.Lsh(a, uint(8*missing))
}
- scope.Stack.push(a)
*pc += size
return nil, nil
}
diff --git a/core/vm/instructions_test.go b/core/vm/instructions_test.go
index dbe055f2ac..354d2ce5ab 100644
--- a/core/vm/instructions_test.go
+++ b/core/vm/instructions_test.go
@@ -25,6 +25,7 @@ import (
"os"
"strings"
"testing"
+ "time"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/math"
@@ -98,7 +99,7 @@ func init() {
func testTwoOperandOp(t *testing.T, tests []TwoOperandTestcase, opFn executionFunc, name string) {
var (
evm = NewEVM(BlockContext{}, nil, params.TestChainConfig, Config{})
- stack = newstack()
+ stack = newStackForTesting()
pc = uint64(0)
)
@@ -109,8 +110,8 @@ func testTwoOperandOp(t *testing.T, tests []TwoOperandTestcase, opFn executionFu
stack.push(x)
stack.push(y)
opFn(&pc, evm, &ScopeContext{nil, stack, nil})
- if len(stack.data) != 1 {
- t.Errorf("Expected one item on stack after %v, got %d: ", name, len(stack.data))
+ if stack.len() != 1 {
+ t.Errorf("Expected one item on stack after %v, got %d: ", name, stack.len())
}
actual := stack.pop()
@@ -196,7 +197,7 @@ func TestSAR(t *testing.T) {
func TestAddMod(t *testing.T) {
var (
evm = NewEVM(BlockContext{}, nil, params.TestChainConfig, Config{})
- stack = newstack()
+ stack = newStackForTesting()
pc = uint64(0)
)
tests := []struct {
@@ -239,7 +240,7 @@ func TestWriteExpectedValues(t *testing.T) {
getResult := func(args []*twoOperandParams, opFn executionFunc) []TwoOperandTestcase {
var (
evm = NewEVM(BlockContext{}, nil, params.TestChainConfig, Config{})
- stack = newstack()
+ stack = newStackForTesting()
pc = uint64(0)
)
result := make([]TwoOperandTestcase, len(args))
@@ -282,23 +283,40 @@ func TestJsonTestcases(t *testing.T) {
func opBenchmark(bench *testing.B, op executionFunc, args ...string) {
var (
- evm = NewEVM(BlockContext{}, nil, params.TestChainConfig, Config{})
- stack = newstack()
- scope = &ScopeContext{nil, stack, nil}
+ evm = NewEVM(BlockContext{}, nil, params.TestChainConfig, Config{})
+ stack = newStackForTesting()
+ code = []byte{}
+ opPush32 = makePush(32, 32)
)
// convert args
intArgs := make([]*uint256.Int, len(args))
for i, arg := range args {
+ code = append(code, common.LeftPadBytes(common.Hex2Bytes(arg), 32)...)
intArgs[i] = new(uint256.Int).SetBytes(common.Hex2Bytes(arg))
}
pc := uint64(0)
- for bench.Loop() {
- for _, arg := range intArgs {
- stack.push(arg)
+ scope := &ScopeContext{nil, stack, &Contract{Code: code}}
+ start := time.Now()
+ bench.ResetTimer()
+ for i := 0; i < bench.N; i++ {
+ for range len(args) {
+ opPush32(&pc, evm, scope)
+ pc += 32
}
op(&pc, evm, scope)
- stack.pop()
+ opPop(&pc, evm, scope)
}
+ bench.StopTimer()
+ elapsed := uint64(time.Since(start))
+ if elapsed < 1 {
+ elapsed = 1
+ }
+ reqGas := uint64(len(args))*GasFastestStep + GasFastestStep + GasQuickStep
+ gasUsed := reqGas * uint64(bench.N)
+ bench.ReportMetric(float64(reqGas), "gas/op")
+ // Keep it as uint64, multiply 100 to get two digit float later
+ mgasps := (100 * 1000 * gasUsed) / elapsed
+ bench.ReportMetric(float64(mgasps)/100, "mgas/s")
for i, arg := range args {
want := new(uint256.Int).SetBytes(common.Hex2Bytes(arg))
@@ -519,7 +537,7 @@ func BenchmarkOpIsZero(b *testing.B) {
func TestOpMstore(t *testing.T) {
var (
evm = NewEVM(BlockContext{}, nil, params.TestChainConfig, Config{})
- stack = newstack()
+ stack = newStackForTesting()
mem = NewMemory()
)
mem.Resize(64)
@@ -542,7 +560,7 @@ func TestOpMstore(t *testing.T) {
func BenchmarkOpMstore(bench *testing.B) {
var (
evm = NewEVM(BlockContext{}, nil, params.TestChainConfig, Config{})
- stack = newstack()
+ stack = newStackForTesting()
mem = NewMemory()
)
mem.Resize(64)
@@ -561,7 +579,7 @@ func TestOpTstore(t *testing.T) {
var (
statedb, _ = state.New(types.EmptyRootHash, state.NewDatabaseForTesting())
evm = NewEVM(BlockContext{}, statedb, params.TestChainConfig, Config{})
- stack = newstack()
+ stack = newStackForTesting()
mem = NewMemory()
caller = common.Address{}
to = common.Address{1}
@@ -600,7 +618,7 @@ func TestOpTstore(t *testing.T) {
func BenchmarkOpKeccak256(bench *testing.B) {
var (
evm = NewEVM(BlockContext{}, nil, params.TestChainConfig, Config{})
- stack = newstack()
+ stack = newStackForTesting()
mem = NewMemory()
)
mem.Resize(32)
@@ -672,7 +690,7 @@ func TestCreate2Addresses(t *testing.T) {
codeHash := crypto.Keccak256(code)
address := crypto.CreateAddress2(origin, salt, codeHash)
/*
- stack := newstack()
+ stack := newStackForTesting()
// salt, but we don't need that for this test
stack.push(big.NewInt(int64(len(code)))) //size
stack.push(big.NewInt(0)) // memstart
@@ -701,12 +719,12 @@ func TestRandom(t *testing.T) {
} {
var (
evm = NewEVM(BlockContext{Random: &tt.random}, nil, params.TestChainConfig, Config{})
- stack = newstack()
+ stack = newStackForTesting()
pc = uint64(0)
)
opRandom(&pc, evm, &ScopeContext{nil, stack, nil})
- if len(stack.data) != 1 {
- t.Errorf("Expected one item on stack after %v, got %d: ", tt.name, len(stack.data))
+ if have, want := stack.len(), 1; have != want {
+ t.Errorf("test '%v': want %d item(s) on stack, have %d: ", tt.name, have, want)
}
actual := stack.pop()
expected, overflow := uint256.FromBig(new(big.Int).SetBytes(tt.random.Bytes()))
@@ -741,14 +759,14 @@ func TestBlobHash(t *testing.T) {
} {
var (
evm = NewEVM(BlockContext{}, nil, params.TestChainConfig, Config{})
- stack = newstack()
+ stack = newStackForTesting()
pc = uint64(0)
)
evm.SetTxContext(TxContext{BlobHashes: tt.hashes})
stack.push(uint256.NewInt(tt.idx))
opBlobHash(&pc, evm, &ScopeContext{nil, stack, nil})
- if len(stack.data) != 1 {
- t.Errorf("Expected one item on stack after %v, got %d: ", tt.name, len(stack.data))
+ if have, want := stack.len(), 1; have != want {
+ t.Errorf("test '%v': want %d item(s) on stack, have %d: ", tt.name, have, want)
}
actual := stack.pop()
expected, overflow := uint256.FromBig(new(big.Int).SetBytes(tt.expect.Bytes()))
@@ -844,7 +862,7 @@ func TestOpMCopy(t *testing.T) {
} {
var (
evm = NewEVM(BlockContext{}, nil, params.TestChainConfig, Config{})
- stack = newstack()
+ stack = newStackForTesting()
pc = uint64(0)
)
data := common.FromHex(strings.ReplaceAll(tc.pre, " ", ""))
@@ -907,7 +925,7 @@ func TestPush(t *testing.T) {
scope := &ScopeContext{
Memory: nil,
- Stack: newstack(),
+ Stack: newStackForTesting(),
Contract: &Contract{
Code: code,
},
@@ -988,7 +1006,7 @@ func TestOpCLZ(t *testing.T) {
}
for _, tc := range tests {
// prepare a fresh stack and PC
- stack := newstack()
+ stack := newStackForTesting()
pc := uint64(0)
// parse input
@@ -1111,7 +1129,7 @@ func TestEIP8024_Execution(t *testing.T) {
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
code := common.FromHex(tc.codeHex)
- stack := newstack()
+ stack := newStackForTesting()
pc := uint64(0)
scope := &ScopeContext{Stack: stack, Contract: &Contract{Code: code}}
var err error
@@ -1189,8 +1207,9 @@ func TestEIP8024_Execution(t *testing.T) {
t.Fatalf("unexpected error: %v", err)
}
got := make([]uint64, 0, stack.len())
- for i := stack.len() - 1; i >= 0; i-- {
- got = append(got, stack.data[i].Uint64())
+ data := stack.Data()
+ for i := len(data) - 1; i >= 0; i-- {
+ got = append(got, data[i].Uint64())
}
if len(got) != len(tc.wantVals) {
t.Fatalf("stack len=%d; want %d", len(got), len(tc.wantVals))
diff --git a/core/vm/interpreter.go b/core/vm/interpreter.go
index 9dc0a0b787..4c278fc857 100644
--- a/core/vm/interpreter.go
+++ b/core/vm/interpreter.go
@@ -116,8 +116,8 @@ func (evm *EVM) Run(contract *Contract, input []byte, readOnly bool) (ret []byte
var (
op OpCode // current opcode
jumpTable *JumpTable = evm.table
- mem = NewMemory() // bound memory
- stack = newstack() // local stack
+ mem = NewMemory() // bound memory
+ stack = evm.arena.stack() // local stack
callContext = &ScopeContext{
Memory: mem,
Stack: stack,
@@ -140,7 +140,7 @@ func (evm *EVM) Run(contract *Contract, input []byte, readOnly bool) (ret []byte
// so that it gets executed _after_: the OnOpcode needs the stacks before
// they are returned to the pools
defer func() {
- returnStack(stack)
+ stack.release()
mem.Free()
}()
contract.Input = input
diff --git a/core/vm/interpreter_test.go b/core/vm/interpreter_test.go
index 69c2316907..868cb12d04 100644
--- a/core/vm/interpreter_test.go
+++ b/core/vm/interpreter_test.go
@@ -83,7 +83,7 @@ func BenchmarkInterpreter(b *testing.B) {
evm = NewEVM(BlockContext{BlockNumber: big.NewInt(1), Time: 1, Random: &common.Hash{}}, statedb, params.MergedTestChainConfig, Config{})
startGas uint64 = 100_000_000
value = uint256.NewInt(0)
- stack = newstack()
+ stack = newStackForTesting()
mem = NewMemory()
contract = NewContract(common.Address{}, common.Address{}, value, NewGasBudget(startGas), nil)
)
diff --git a/core/vm/memory_table.go b/core/vm/memory_table.go
index 63ad967850..8f30cbeee6 100644
--- a/core/vm/memory_table.go
+++ b/core/vm/memory_table.go
@@ -17,59 +17,59 @@
package vm
func memoryKeccak256(stack *Stack) (uint64, bool) {
- return calcMemSize64(stack.Back(0), stack.Back(1))
+ return calcMemSize64(stack.back(0), stack.back(1))
}
func memoryCallDataCopy(stack *Stack) (uint64, bool) {
- return calcMemSize64(stack.Back(0), stack.Back(2))
+ return calcMemSize64(stack.back(0), stack.back(2))
}
func memoryReturnDataCopy(stack *Stack) (uint64, bool) {
- return calcMemSize64(stack.Back(0), stack.Back(2))
+ return calcMemSize64(stack.back(0), stack.back(2))
}
func memoryCodeCopy(stack *Stack) (uint64, bool) {
- return calcMemSize64(stack.Back(0), stack.Back(2))
+ return calcMemSize64(stack.back(0), stack.back(2))
}
func memoryExtCodeCopy(stack *Stack) (uint64, bool) {
- return calcMemSize64(stack.Back(1), stack.Back(3))
+ return calcMemSize64(stack.back(1), stack.back(3))
}
func memoryMLoad(stack *Stack) (uint64, bool) {
- return calcMemSize64WithUint(stack.Back(0), 32)
+ return calcMemSize64WithUint(stack.back(0), 32)
}
func memoryMStore8(stack *Stack) (uint64, bool) {
- return calcMemSize64WithUint(stack.Back(0), 1)
+ return calcMemSize64WithUint(stack.back(0), 1)
}
func memoryMStore(stack *Stack) (uint64, bool) {
- return calcMemSize64WithUint(stack.Back(0), 32)
+ return calcMemSize64WithUint(stack.back(0), 32)
}
func memoryMcopy(stack *Stack) (uint64, bool) {
- mStart := stack.Back(0) // stack[0]: dest
- if stack.Back(1).Gt(mStart) {
- mStart = stack.Back(1) // stack[1]: source
+ mStart := stack.back(0) // stack[0]: dest
+ if stack.back(1).Gt(mStart) {
+ mStart = stack.back(1) // stack[1]: source
}
- return calcMemSize64(mStart, stack.Back(2)) // stack[2]: length
+ return calcMemSize64(mStart, stack.back(2)) // stack[2]: length
}
func memoryCreate(stack *Stack) (uint64, bool) {
- return calcMemSize64(stack.Back(1), stack.Back(2))
+ return calcMemSize64(stack.back(1), stack.back(2))
}
func memoryCreate2(stack *Stack) (uint64, bool) {
- return calcMemSize64(stack.Back(1), stack.Back(2))
+ return calcMemSize64(stack.back(1), stack.back(2))
}
func memoryCall(stack *Stack) (uint64, bool) {
- x, overflow := calcMemSize64(stack.Back(5), stack.Back(6))
+ x, overflow := calcMemSize64(stack.back(5), stack.back(6))
if overflow {
return 0, true
}
- y, overflow := calcMemSize64(stack.Back(3), stack.Back(4))
+ y, overflow := calcMemSize64(stack.back(3), stack.back(4))
if overflow {
return 0, true
}
@@ -80,11 +80,11 @@ func memoryCall(stack *Stack) (uint64, bool) {
}
func memoryDelegateCall(stack *Stack) (uint64, bool) {
- x, overflow := calcMemSize64(stack.Back(4), stack.Back(5))
+ x, overflow := calcMemSize64(stack.back(4), stack.back(5))
if overflow {
return 0, true
}
- y, overflow := calcMemSize64(stack.Back(2), stack.Back(3))
+ y, overflow := calcMemSize64(stack.back(2), stack.back(3))
if overflow {
return 0, true
}
@@ -95,11 +95,11 @@ func memoryDelegateCall(stack *Stack) (uint64, bool) {
}
func memoryStaticCall(stack *Stack) (uint64, bool) {
- x, overflow := calcMemSize64(stack.Back(4), stack.Back(5))
+ x, overflow := calcMemSize64(stack.back(4), stack.back(5))
if overflow {
return 0, true
}
- y, overflow := calcMemSize64(stack.Back(2), stack.Back(3))
+ y, overflow := calcMemSize64(stack.back(2), stack.back(3))
if overflow {
return 0, true
}
@@ -110,13 +110,13 @@ func memoryStaticCall(stack *Stack) (uint64, bool) {
}
func memoryReturn(stack *Stack) (uint64, bool) {
- return calcMemSize64(stack.Back(0), stack.Back(1))
+ return calcMemSize64(stack.back(0), stack.back(1))
}
func memoryRevert(stack *Stack) (uint64, bool) {
- return calcMemSize64(stack.Back(0), stack.Back(1))
+ return calcMemSize64(stack.back(0), stack.back(1))
}
func memoryLog(stack *Stack) (uint64, bool) {
- return calcMemSize64(stack.Back(0), stack.Back(1))
+ return calcMemSize64(stack.back(0), stack.back(1))
}
diff --git a/core/vm/operations_acl.go b/core/vm/operations_acl.go
index 313d03819e..86ac262a93 100644
--- a/core/vm/operations_acl.go
+++ b/core/vm/operations_acl.go
@@ -37,7 +37,7 @@ func makeGasSStoreFunc(clearingRefund uint64) gasFunc {
}
// Gas sentry honoured, do the actual gas calculation based on the stored value
var (
- y, x = stack.Back(1), stack.peek()
+ y, x = stack.back(1), stack.peek()
slot = common.Hash(x.Bytes32())
current, original = evm.StateDB.GetStateAndCommittedState(contract.Address(), slot)
cost = uint64(0)
@@ -158,7 +158,7 @@ func gasEip2929AccountCheck(evm *EVM, contract *Contract, stack *Stack, mem *Mem
func makeCallVariantGasCallEIP2929(oldCalculator gasFunc, addressPosition int) gasFunc {
return func(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (GasCosts, error) {
- addr := common.Address(stack.Back(addressPosition).Bytes20())
+ addr := common.Address(stack.back(addressPosition).Bytes20())
// Check slot presence in the access list
warmAccess := evm.StateDB.AddressInAccessList(addr)
// The WarmStorageReadCostEIP2929 (100) is already deducted in the form of a constant cost, so
@@ -269,7 +269,7 @@ func gasCallEIP7702(evm *EVM, contract *Contract, stack *Stack, mem *Memory, mem
// Although it's checked in `gasCall`, EIP-7702 loads the target's code before
// to determine if it is resolving a delegation. This could incorrectly record
// the target in the block access list (BAL) if the call later fails.
- transfersValue := !stack.Back(2).IsZero()
+ transfersValue := !stack.back(2).IsZero()
if evm.readOnly && transfersValue {
return GasCosts{}, ErrWriteProtection
}
@@ -281,7 +281,7 @@ func makeCallVariantGasCallEIP7702(intrinsicFunc gasFunc) gasFunc {
var (
eip2929Cost uint64
eip7702Cost uint64
- addr = common.Address(stack.Back(1).Bytes20())
+ addr = common.Address(stack.back(1).Bytes20())
)
// Perform EIP-2929 checks (stateless), checking address presence
// in the accessList and charge the cold access accordingly.
@@ -330,7 +330,7 @@ func makeCallVariantGasCallEIP7702(intrinsicFunc gasFunc) gasFunc {
}
// Calculate the gas budget for the nested call. The costs defined by
// EIP-2929 and EIP-7702 have already been applied.
- evm.callGasTemp, err = callGas(evm.chainRules.IsEIP150, contract.Gas.RegularGas, intrinsicCost.RegularGas, stack.Back(0))
+ evm.callGasTemp, err = callGas(evm.chainRules.IsEIP150, contract.Gas.RegularGas, intrinsicCost.RegularGas, stack.back(0))
if err != nil {
return GasCosts{}, err
}
diff --git a/core/vm/operations_verkle.go b/core/vm/operations_verkle.go
index d57f2c4dcf..4d3960a174 100644
--- a/core/vm/operations_verkle.go
+++ b/core/vm/operations_verkle.go
@@ -56,7 +56,7 @@ func gasExtCodeHash4762(evm *EVM, contract *Contract, stack *Stack, mem *Memory,
func makeCallVariantGasEIP4762(oldCalculator gasFunc, withTransferCosts bool) gasFunc {
return func(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (GasCosts, error) {
var (
- target = common.Address(stack.Back(1).Bytes20())
+ target = common.Address(stack.back(1).Bytes20())
witnessGas uint64
_, isPrecompile = evm.precompile(target)
isSystemContract = target == params.HistoryStorageAddress
@@ -64,7 +64,7 @@ func makeCallVariantGasEIP4762(oldCalculator gasFunc, withTransferCosts bool) ga
// If value is transferred, it is charged before 1/64th
// is subtracted from the available gas pool.
- if withTransferCosts && !stack.Back(2).IsZero() {
+ if withTransferCosts && !stack.back(2).IsZero() {
wantedValueTransferWitnessGas := evm.AccessEvents.ValueTransferGas(contract.Address(), target, contract.Gas.RegularGas)
if wantedValueTransferWitnessGas > contract.Gas.RegularGas {
return GasCosts{RegularGas: wantedValueTransferWitnessGas}, nil
@@ -168,8 +168,8 @@ func gasCodeCopyEip4762(evm *EVM, contract *Contract, stack *Stack, mem *Memory,
gas := gasCost.RegularGas
if !contract.IsDeployment && !contract.IsSystemCall {
var (
- codeOffset = stack.Back(1)
- length = stack.Back(2)
+ codeOffset = stack.back(1)
+ length = stack.back(2)
)
uint64CodeOffset, overflow := codeOffset.Uint64WithOverflow()
if overflow {
diff --git a/core/vm/runtime/runtime_test.go b/core/vm/runtime/runtime_test.go
index 40fb770454..bc2ffd622d 100644
--- a/core/vm/runtime/runtime_test.go
+++ b/core/vm/runtime/runtime_test.go
@@ -959,3 +959,63 @@ func TestDelegatedAccountAccessCost(t *testing.T) {
}
}
}
+
+func TestManyLargeStacks(t *testing.T) {
+ // This piece of code will push 512 items to the stack, and then call itself
+ // recursively.
+ code := make([]byte, 10)
+ for i := range code {
+ code[i] = byte(vm.PUSH0)
+ }
+ code = append(code, []byte{
+ byte(vm.ADDRESS), // address to call
+ byte(vm.GAS),
+ byte(vm.CALL),
+ }...)
+
+ main := common.HexToAddress("0xbb")
+ statedb, _ := state.New(types.EmptyRootHash, state.NewDatabaseForTesting())
+ statedb.SetCode(main, code, tracing.CodeChangeUnspecified)
+
+ //tracer := logger.NewJSONLogger(nil, os.Stdout)
+ var tracer *tracing.Hooks
+ _, _, err := Call(main, nil, &Config{
+ GasLimit: 10_000_000,
+ State: statedb,
+ EVMConfig: vm.Config{
+ Tracer: tracer,
+ }})
+ if err != nil {
+ t.Fatal("didn't expect error", err)
+ }
+}
+
+func BenchmarkLargeDeepStacks(b *testing.B) {
+ // This piece of code will push 512 items to the stack, and then call itself
+ // recursively.
+ code := make([]byte, 512)
+ for i := range code {
+ code[i] = byte(vm.PUSH0)
+ }
+ code = append(code, []byte{
+ byte(vm.ADDRESS), // address to call
+ byte(vm.GAS),
+ byte(vm.CALL),
+ }...)
+ benchmarkNonModifyingCode(10_000_000, code, "deep-large-stacks-10M", "", b)
+}
+
+func BenchmarkShortDeepStacks(b *testing.B) {
+ // This piece of code will push a few items to the stack, and then call itself
+ // recursively.
+ code := make([]byte, 8)
+ for i := range code {
+ code[i] = byte(vm.PUSH0)
+ }
+ code = append(code, []byte{
+ byte(vm.ADDRESS), // address to call
+ byte(vm.GAS),
+ byte(vm.CALL),
+ }...)
+ benchmarkNonModifyingCode(10_000_000, code, "deep-short-stacks-10M", "", b)
+}
diff --git a/core/vm/stack.go b/core/vm/stack.go
index 879dc9aa6d..d8000bc86d 100644
--- a/core/vm/stack.go
+++ b/core/vm/stack.go
@@ -17,111 +17,170 @@
package vm
import (
+ "slices"
"sync"
"github.com/holiman/uint256"
)
+// stackArena is an arena which actual evm stacks use for data storage
+type stackArena struct {
+ data []uint256.Int
+ top int // first free slot
+}
+
+func newArena() *stackArena {
+ return stackPool.Get().(*stackArena)
+}
+
+// 1025, because in stack() there is a condition check
+// for the stack size that would fail if it was set to
+// 1024.
+const initialStackSize = 1025
+
var stackPool = sync.Pool{
- New: func() interface{} {
- return &Stack{data: make([]uint256.Int, 0, 16)}
+ New: func() any {
+ return &stackArena{
+ data: make([]uint256.Int, initialStackSize),
+ }
},
}
+func returnStack(arena *stackArena) {
+ arena.top = 0 // defensive, not strictly needed as s.inner.top = s.bottom in release()
+ stackPool.Put(arena)
+}
+
+// stack returns an instance of a stack which uses the underlying arena. The instance
+// must be released by invoking the (*Stack).release() method
+func (sa *stackArena) stack() *Stack {
+ // make sure every substack has at least 1024 elements
+ if len(sa.data) <= sa.top+1024 {
+ // we need to grow the arena
+ sa.data = slices.Grow(sa.data, 1024)
+ sa.data = sa.data[:cap(sa.data)]
+ }
+ return &Stack{
+ bottom: sa.top,
+ size: 0,
+ inner: sa,
+ }
+}
+
+// newStackForTesting is meant to be used solely for testing. It creates a stack
+// backed by a newly allocated arena.
+func newStackForTesting() *Stack {
+ arena := &stackArena{
+ data: make([]uint256.Int, 1025),
+ }
+ return arena.stack()
+}
+
// Stack is an object for basic stack operations. Items popped to the stack are
// expected to be changed and modified. stack does not take care of adding newly
// initialized objects.
type Stack struct {
- data []uint256.Int
+ bottom int // bottom is the index of the first element of this stack
+ size int // size is the number of elements in this stack
+ inner *stackArena
}
-func newstack() *Stack {
- return stackPool.Get().(*Stack)
-}
-
-func returnStack(s *Stack) {
- s.data = s.data[:0]
- stackPool.Put(s)
+// release un-claims the area of the arena which was claimed by the stack.
+func (s *Stack) release() {
+ // When the stack is returned, need to notify the arena that the new 'top' is
+ // the returned stack's bottom.
+ s.inner.top = s.bottom
}
// Data returns the underlying uint256.Int array.
-func (st *Stack) Data() []uint256.Int {
- return st.data
+func (s *Stack) Data() []uint256.Int {
+ return s.inner.data[s.bottom : s.bottom+s.size]
}
-func (st *Stack) push(d *uint256.Int) {
- // NOTE push limit (1024) is checked in baseCheck
- st.data = append(st.data, *d)
+func (s *Stack) push(d *uint256.Int) {
+ elem := s.get()
+ *elem = *d
}
-func (st *Stack) pop() (ret uint256.Int) {
- ret = st.data[len(st.data)-1]
- st.data = st.data[:len(st.data)-1]
- return
+// get returns a pointer to a newly created element
+// on top of the stack
+func (s *Stack) get() *uint256.Int {
+ elem := &s.inner.data[s.inner.top]
+ s.inner.top++
+ s.size++
+ return elem
}
-func (st *Stack) len() int {
- return len(st.data)
+func (s *Stack) pop() uint256.Int {
+ s.inner.top--
+ s.size--
+ return s.inner.data[s.inner.top]
}
-func (st *Stack) swap1() {
- st.data[st.len()-2], st.data[st.len()-1] = st.data[st.len()-1], st.data[st.len()-2]
-}
-func (st *Stack) swap2() {
- st.data[st.len()-3], st.data[st.len()-1] = st.data[st.len()-1], st.data[st.len()-3]
-}
-func (st *Stack) swap3() {
- st.data[st.len()-4], st.data[st.len()-1] = st.data[st.len()-1], st.data[st.len()-4]
-}
-func (st *Stack) swap4() {
- st.data[st.len()-5], st.data[st.len()-1] = st.data[st.len()-1], st.data[st.len()-5]
-}
-func (st *Stack) swap5() {
- st.data[st.len()-6], st.data[st.len()-1] = st.data[st.len()-1], st.data[st.len()-6]
-}
-func (st *Stack) swap6() {
- st.data[st.len()-7], st.data[st.len()-1] = st.data[st.len()-1], st.data[st.len()-7]
-}
-func (st *Stack) swap7() {
- st.data[st.len()-8], st.data[st.len()-1] = st.data[st.len()-1], st.data[st.len()-8]
-}
-func (st *Stack) swap8() {
- st.data[st.len()-9], st.data[st.len()-1] = st.data[st.len()-1], st.data[st.len()-9]
-}
-func (st *Stack) swap9() {
- st.data[st.len()-10], st.data[st.len()-1] = st.data[st.len()-1], st.data[st.len()-10]
-}
-func (st *Stack) swap10() {
- st.data[st.len()-11], st.data[st.len()-1] = st.data[st.len()-1], st.data[st.len()-11]
-}
-func (st *Stack) swap11() {
- st.data[st.len()-12], st.data[st.len()-1] = st.data[st.len()-1], st.data[st.len()-12]
-}
-func (st *Stack) swap12() {
- st.data[st.len()-13], st.data[st.len()-1] = st.data[st.len()-1], st.data[st.len()-13]
-}
-func (st *Stack) swap13() {
- st.data[st.len()-14], st.data[st.len()-1] = st.data[st.len()-1], st.data[st.len()-14]
-}
-func (st *Stack) swap14() {
- st.data[st.len()-15], st.data[st.len()-1] = st.data[st.len()-1], st.data[st.len()-15]
-}
-func (st *Stack) swap15() {
- st.data[st.len()-16], st.data[st.len()-1] = st.data[st.len()-1], st.data[st.len()-16]
-}
-func (st *Stack) swap16() {
- st.data[st.len()-17], st.data[st.len()-1] = st.data[st.len()-1], st.data[st.len()-17]
+func (s *Stack) len() int {
+ return s.size
}
-func (st *Stack) dup(n int) {
- st.push(&st.data[st.len()-n])
+func (s *Stack) swap1() {
+ s.inner.data[s.bottom+s.size-2], s.inner.data[s.bottom+s.size-1] = s.inner.data[s.bottom+s.size-1], s.inner.data[s.bottom+s.size-2]
+}
+func (s *Stack) swap2() {
+ s.inner.data[s.bottom+s.size-3], s.inner.data[s.bottom+s.size-1] = s.inner.data[s.bottom+s.size-1], s.inner.data[s.bottom+s.size-3]
+}
+func (s *Stack) swap3() {
+ s.inner.data[s.bottom+s.size-4], s.inner.data[s.bottom+s.size-1] = s.inner.data[s.bottom+s.size-1], s.inner.data[s.bottom+s.size-4]
+}
+func (s *Stack) swap4() {
+ s.inner.data[s.bottom+s.size-5], s.inner.data[s.bottom+s.size-1] = s.inner.data[s.bottom+s.size-1], s.inner.data[s.bottom+s.size-5]
+}
+func (s *Stack) swap5() {
+ s.inner.data[s.bottom+s.size-6], s.inner.data[s.bottom+s.size-1] = s.inner.data[s.bottom+s.size-1], s.inner.data[s.bottom+s.size-6]
+}
+func (s *Stack) swap6() {
+ s.inner.data[s.bottom+s.size-7], s.inner.data[s.bottom+s.size-1] = s.inner.data[s.bottom+s.size-1], s.inner.data[s.bottom+s.size-7]
+}
+func (s *Stack) swap7() {
+ s.inner.data[s.bottom+s.size-8], s.inner.data[s.bottom+s.size-1] = s.inner.data[s.bottom+s.size-1], s.inner.data[s.bottom+s.size-8]
+}
+func (s *Stack) swap8() {
+ s.inner.data[s.bottom+s.size-9], s.inner.data[s.bottom+s.size-1] = s.inner.data[s.bottom+s.size-1], s.inner.data[s.bottom+s.size-9]
+}
+func (s *Stack) swap9() {
+ s.inner.data[s.bottom+s.size-10], s.inner.data[s.bottom+s.size-1] = s.inner.data[s.bottom+s.size-1], s.inner.data[s.bottom+s.size-10]
+}
+func (s *Stack) swap10() {
+ s.inner.data[s.bottom+s.size-11], s.inner.data[s.bottom+s.size-1] = s.inner.data[s.bottom+s.size-1], s.inner.data[s.bottom+s.size-11]
+}
+func (s *Stack) swap11() {
+ s.inner.data[s.bottom+s.size-12], s.inner.data[s.bottom+s.size-1] = s.inner.data[s.bottom+s.size-1], s.inner.data[s.bottom+s.size-12]
+}
+func (s *Stack) swap12() {
+ s.inner.data[s.bottom+s.size-13], s.inner.data[s.bottom+s.size-1] = s.inner.data[s.bottom+s.size-1], s.inner.data[s.bottom+s.size-13]
+}
+func (s *Stack) swap13() {
+ s.inner.data[s.bottom+s.size-14], s.inner.data[s.bottom+s.size-1] = s.inner.data[s.bottom+s.size-1], s.inner.data[s.bottom+s.size-14]
+}
+func (s *Stack) swap14() {
+ s.inner.data[s.bottom+s.size-15], s.inner.data[s.bottom+s.size-1] = s.inner.data[s.bottom+s.size-1], s.inner.data[s.bottom+s.size-15]
+}
+func (s *Stack) swap15() {
+ s.inner.data[s.bottom+s.size-16], s.inner.data[s.bottom+s.size-1] = s.inner.data[s.bottom+s.size-1], s.inner.data[s.bottom+s.size-16]
+}
+func (s *Stack) swap16() {
+ s.inner.data[s.bottom+s.size-17], s.inner.data[s.bottom+s.size-1] = s.inner.data[s.bottom+s.size-1], s.inner.data[s.bottom+s.size-17]
}
-func (st *Stack) peek() *uint256.Int {
- return &st.data[st.len()-1]
+func (s *Stack) dup(n int) {
+ s.inner.data[s.bottom+s.size] = s.inner.data[s.bottom+s.size-n]
+ s.size++
+ s.inner.top++
}
-// Back returns the n'th item in stack
-func (st *Stack) Back(n int) *uint256.Int {
- return &st.data[st.len()-n-1]
+func (s *Stack) peek() *uint256.Int {
+ return &s.inner.data[s.bottom+s.size-1]
+}
+
+// back returns the n'th item in stack
+func (s *Stack) back(n int) *uint256.Int {
+ return &s.inner.data[s.bottom+s.size-n-1]
}
diff --git a/eth/gasestimator/gasestimator.go b/eth/gasestimator/gasestimator.go
index ad6491fd93..ace0752037 100644
--- a/eth/gasestimator/gasestimator.go
+++ b/eth/gasestimator/gasestimator.go
@@ -63,10 +63,10 @@ func Estimate(ctx context.Context, call *core.Message, opts *Options, gasCap uin
}
// Cap the maximum gas allowance according to EIP-7825 if the estimation targets Osaka
- if hi > params.MaxTxGas {
- if opts.Config.IsOsaka(opts.Header.Number, opts.Header.Time) {
- hi = params.MaxTxGas
- }
+ isOsaka := opts.Config.IsOsaka(opts.Header.Number, opts.Header.Time)
+ isAmsterdam := opts.Config.IsAmsterdam(opts.Header.Number, opts.Header.Time)
+ if hi > params.MaxTxGas && isOsaka && !isAmsterdam {
+ hi = params.MaxTxGas
}
// Normalize the max fee per gas the call is willing to spend.
@@ -244,6 +244,7 @@ func run(ctx context.Context, call *core.Message, opts *Options) (*core.Executio
evmContext.BlobBaseFee = new(big.Int)
}
evm := vm.NewEVM(evmContext, dirtyState, opts.Config, vm.Config{NoBaseFee: true})
+ defer evm.Release()
// Monitor the outer context and interrupt the EVM upon cancellation. To avoid
// a dangling goroutine until the outer estimation finishes, create an internal
diff --git a/eth/state_accessor.go b/eth/state_accessor.go
index 7467e1e590..a806a4fc56 100644
--- a/eth/state_accessor.go
+++ b/eth/state_accessor.go
@@ -247,6 +247,7 @@ func (eth *Ethereum) stateAtTransaction(ctx context.Context, block *types.Block,
// Insert parent beacon block root in the state as per EIP-4788.
context := core.NewEVMBlockContext(block.Header(), eth.blockchain, nil)
evm := vm.NewEVM(context, statedb, eth.blockchain.Config(), vm.Config{})
+ defer evm.Release()
if beaconRoot := block.BeaconRoot(); beaconRoot != nil {
core.ProcessBeaconBlockRoot(*beaconRoot, evm)
}
diff --git a/eth/tracers/api.go b/eth/tracers/api.go
index b5ddc78a10..dae11b81de 100644
--- a/eth/tracers/api.go
+++ b/eth/tracers/api.go
@@ -379,6 +379,7 @@ func (api *API) traceChain(start, end *types.Block, config *TraceConfig, closed
if api.backend.ChainConfig().IsPrague(next.Number(), next.Time()) {
core.ProcessParentBlockHash(next.ParentHash(), evm)
}
+ evm.Release()
// Clean out any pending release functions of trace state. Note this
// step must be done after constructing tracing state, because the
// tracing state of block next depends on the parent state and construction
@@ -524,6 +525,7 @@ func (api *API) IntermediateRoots(ctx context.Context, hash common.Hash, config
deleteEmptyObjects = chainConfig.IsEIP158(block.Number())
)
evm := vm.NewEVM(vmctx, statedb, chainConfig, vm.Config{})
+ defer evm.Release()
if beaconRoot := block.BeaconRoot(); beaconRoot != nil {
core.ProcessBeaconBlockRoot(*beaconRoot, evm)
}
@@ -584,6 +586,7 @@ func (api *API) traceBlock(ctx context.Context, block *types.Block, config *Trac
blockCtx := core.NewEVMBlockContext(block.Header(), api.chainContext(ctx), nil)
evm := vm.NewEVM(blockCtx, statedb, api.backend.ChainConfig(), vm.Config{})
+ defer evm.Release()
if beaconRoot := block.BeaconRoot(); beaconRoot != nil {
core.ProcessBeaconBlockRoot(*beaconRoot, evm)
}
@@ -673,6 +676,7 @@ func (api *API) traceBlockParallel(ctx context.Context, block *types.Block, stat
var failed error
blockCtx := core.NewEVMBlockContext(block.Header(), api.chainContext(ctx), nil)
evm := vm.NewEVM(blockCtx, statedb, api.backend.ChainConfig(), vm.Config{})
+ defer evm.Release()
txloop:
for i, tx := range txs {
@@ -758,6 +762,7 @@ func (api *API) standardTraceBlockToFile(ctx context.Context, block *types.Block
}
evm := vm.NewEVM(vmctx, statedb, chainConfig, vm.Config{})
+ defer evm.Release()
if beaconRoot := block.BeaconRoot(); beaconRoot != nil {
core.ProcessBeaconBlockRoot(*beaconRoot, evm)
}
@@ -805,6 +810,7 @@ func (api *API) standardTraceBlockToFile(ctx context.Context, block *types.Block
tracer.OnTxStart(evm.GetVMContext(), tx, msg.From)
}
_, err = core.ApplyMessage(evm, msg, nil)
+ evm.Release()
if writer != nil {
writer.Flush()
}
@@ -817,7 +823,7 @@ func (api *API) standardTraceBlockToFile(ctx context.Context, block *types.Block
}
// Finalize the state so any modifications are written to the trie
// Only delete empty objects if EIP158/161 (a.k.a Spurious Dragon) is in effect
- statedb.Finalise(evm.ChainConfig().IsEIP158(block.Number()))
+ statedb.Finalise(chainConfig.IsEIP158(block.Number()))
// If we've traced the transaction we were looking for, abort
if tx.Hash() == txHash {
@@ -999,6 +1005,7 @@ func (api *API) traceTx(ctx context.Context, tx *types.Transaction, message *cor
}
tracingStateDB := state.NewHookedState(statedb, tracer.Hooks)
evm := vm.NewEVM(vmctx, tracingStateDB, api.backend.ChainConfig(), vm.Config{Tracer: tracer.Hooks, NoBaseFee: true})
+ defer evm.Release()
if precompiles != nil {
evm.SetPrecompiles(precompiles)
}
diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go
index 149e12c5b8..e8669b86c6 100644
--- a/internal/ethapi/api.go
+++ b/internal/ethapi/api.go
@@ -775,6 +775,7 @@ func applyMessage(ctx context.Context, b Backend, args TransactionArgs, state *s
blockContext.BlobBaseFee = new(big.Int)
}
evm := b.GetEVM(ctx, state, header, vmConfig, blockContext)
+ defer evm.Release()
if precompiles != nil {
evm.SetPrecompiles(precompiles)
}
@@ -1390,6 +1391,7 @@ func AccessList(ctx context.Context, b Backend, blockNrOrHash rpc.BlockNumberOrH
evm.Context.BlobBaseFee = new(big.Int)
}
res, err := core.ApplyMessage(evm, msg, nil)
+ evm.Release()
if err != nil {
return nil, 0, nil, fmt.Errorf("failed to apply transaction: %v err: %v", args.ToTransaction(types.LegacyTxType).Hash(), err)
}
diff --git a/internal/ethapi/simulate.go b/internal/ethapi/simulate.go
index d18561760e..e3a14bf5d6 100644
--- a/internal/ethapi/simulate.go
+++ b/internal/ethapi/simulate.go
@@ -312,6 +312,7 @@ func (sim *simulator) processBlock(ctx context.Context, block *simBlock, header,
tracingStateDB = state.NewHookedState(sim.state, hooks)
}
evm := vm.NewEVM(blockContext, tracingStateDB, sim.chainConfig, *vmConfig)
+ defer evm.Release()
// It is possible to override precompiles with EVM bytecode, or
// move them to another address.
if precompiles != nil {
diff --git a/miner/worker.go b/miner/worker.go
index ae5d6c306f..42e3695025 100644
--- a/miner/worker.go
+++ b/miner/worker.go
@@ -83,6 +83,9 @@ func (env *environment) txFitsSize(tx *types.Transaction) bool {
// discard terminates the background threads before discarding it.
func (env *environment) discard() {
env.state.StopPrefetcher()
+ if env.evm != nil {
+ env.evm.Release()
+ }
}
const (
@@ -560,7 +563,7 @@ func (miner *Miner) fillTransactions(ctx context.Context, interrupt *atomic.Int3
if env.header.ExcessBlobGas != nil {
filter.BlobFee = uint256.MustFromBig(eip4844.CalcBlobFee(miner.chainConfig, env.header))
}
- if miner.chainConfig.IsOsaka(env.header.Number, env.header.Time) {
+ if miner.chainConfig.IsOsaka(env.header.Number, env.header.Time) && !miner.chainConfig.IsAmsterdam(env.header.Number, env.header.Time) {
filter.GasLimitCap = params.MaxTxGas
}
filter.BlobTxs = false
diff --git a/p2p/discover/common.go b/p2p/discover/common.go
index 767cc23b92..5513afd54d 100644
--- a/p2p/discover/common.go
+++ b/p2p/discover/common.go
@@ -17,9 +17,11 @@
package discover
import (
+ "container/list"
"crypto/ecdsa"
crand "crypto/rand"
"encoding/binary"
+ "iter"
"math/rand"
"net"
"net/netip"
@@ -143,3 +145,16 @@ func (r *reseedingRandom) Shuffle(n int, swap func(i, j int)) {
defer r.mu.Unlock()
r.cur.Shuffle(n, swap)
}
+
+// iterList iterates over the elements of the given list.
+func iterList[T any](l *list.List) iter.Seq2[T, *list.Element] {
+ return func(yield func(T, *list.Element) bool) {
+ for el := l.Front(); el != nil; {
+ next := el.Next()
+ if !yield(el.Value.(T), el) {
+ return
+ }
+ el = next
+ }
+ }
+}
diff --git a/p2p/discover/v4_udp.go b/p2p/discover/v4_udp.go
index dd3f363f7e..9e824dae18 100644
--- a/p2p/discover/v4_udp.go
+++ b/p2p/discover/v4_udp.go
@@ -446,9 +446,8 @@ func (t *UDPv4) loop() {
}
// Start the timer so it fires when the next pending reply has expired.
now := time.Now()
- for el := plist.Front(); el != nil; el = el.Next() {
- nextTimeout = el.Value.(*replyMatcher)
- if dist := nextTimeout.deadline.Sub(now); dist < 2*respTimeout {
+ for p, el := range iterList[*replyMatcher](plist) {
+ if dist := p.deadline.Sub(now); dist < 2*respTimeout {
timeout.Reset(dist)
return
}
@@ -478,8 +477,7 @@ func (t *UDPv4) loop() {
case r := <-t.gotreply:
var matched bool // whether any replyMatcher considered the reply acceptable.
- for el := plist.Front(); el != nil; el = el.Next() {
- p := el.Value.(*replyMatcher)
+ for p, el := range iterList[*replyMatcher](plist) {
if p.from == r.from && p.ptype == r.data.Kind() && p.ip == r.ip {
ok, requestDone := p.callback(r.data)
matched = matched || ok
@@ -499,8 +497,7 @@ func (t *UDPv4) loop() {
nextTimeout = nil
// Notify and remove callbacks whose deadline is in the past.
- for el := plist.Front(); el != nil; el = el.Next() {
- p := el.Value.(*replyMatcher)
+ for p, el := range iterList[*replyMatcher](plist) {
if now.After(p.deadline) || now.Equal(p.deadline) {
p.errc <- errTimeout
plist.Remove(el)