From a7efdcbf095fa043390c98439e6924b69ed3f80d Mon Sep 17 00:00:00 2001 From: Micke <155267459+reallesee@users.noreply.github.com> Date: Tue, 22 Jul 2025 23:06:48 +0200 Subject: [PATCH 01/23] p2p/rlpx: optimize XOR operation using bitutil.XORBytes (#32217) Replace manual byte-by-byte XOR implementation with the optimized bitutil.XORBytes function. This improves performance by using word-sized operations on supported architectures while maintaining the same functionality. The optimized version processes data in bulk rather than one byte at a time --------- Co-authored-by: Felix Lange --- p2p/rlpx/rlpx.go | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/p2p/rlpx/rlpx.go b/p2p/rlpx/rlpx.go index dd14822dee7..c074534d4de 100644 --- a/p2p/rlpx/rlpx.go +++ b/p2p/rlpx/rlpx.go @@ -33,6 +33,7 @@ import ( "net" "time" + "github.com/ethereum/go-ethereum/common/bitutil" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto/ecies" "github.com/ethereum/go-ethereum/rlp" @@ -676,8 +677,6 @@ func exportPubkey(pub *ecies.PublicKey) []byte { func xor(one, other []byte) (xor []byte) { xor = make([]byte, len(one)) - for i := 0; i < len(one); i++ { - xor[i] = one[i] ^ other[i] - } + bitutil.XORBytes(xor, one, other) return xor } From 3b67602c4c5e701028c7246977aabff02cce643c Mon Sep 17 00:00:00 2001 From: gzeon Date: Wed, 23 Jul 2025 12:41:37 +0900 Subject: [PATCH 02/23] eth/gasestimator: fix potential overflow (#32255) Improve binary search, preventing the potential overflow in certain L2 cases --- eth/gasestimator/gasestimator.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/eth/gasestimator/gasestimator.go b/eth/gasestimator/gasestimator.go index 98a4f74b3e4..7e9d8125de8 100644 --- a/eth/gasestimator/gasestimator.go +++ b/eth/gasestimator/gasestimator.go @@ -170,7 +170,7 @@ func Estimate(ctx context.Context, call *core.Message, opts *Options, gasCap uin break } } - mid := (hi + lo) / 2 + mid := lo + (hi-lo)/2 if mid > lo*2 { // Most txs don't need much higher gas limit than their gas used, and most txs don't // require near the full block limit of gas, so the selection of where to bisect the From 16117eb7cddc4584865af106d2332aa89f387d3d Mon Sep 17 00:00:00 2001 From: Delweng Date: Wed, 23 Jul 2025 15:12:55 +0800 Subject: [PATCH 03/23] triedb/pathdb: fix an deadlock in history indexer (#32260) Seems the `signal.result` was not sent back in shorten case, this will cause a deadlock. --------- Signed-off-by: jsvisa Co-authored-by: Gary Rong --- triedb/pathdb/history_indexer.go | 15 ++++--- triedb/pathdb/history_indexer_test.go | 57 +++++++++++++++++++++++++++ 2 files changed, 66 insertions(+), 6 deletions(-) create mode 100644 triedb/pathdb/history_indexer_test.go diff --git a/triedb/pathdb/history_indexer.go b/triedb/pathdb/history_indexer.go index 42103fab32c..054d43e946d 100644 --- a/triedb/pathdb/history_indexer.go +++ b/triedb/pathdb/history_indexer.go @@ -392,16 +392,17 @@ func (i *indexIniter) run(lastID uint64) { select { case signal := <-i.interrupt: // The indexing limit can only be extended or shortened continuously. - if signal.newLastID != lastID+1 && signal.newLastID != lastID-1 { - signal.result <- fmt.Errorf("invalid history id, last: %d, got: %d", lastID, signal.newLastID) + newLastID := signal.newLastID + if newLastID != lastID+1 && newLastID != lastID-1 { + signal.result <- fmt.Errorf("invalid history id, last: %d, got: %d", lastID, newLastID) continue } - i.last.Store(signal.newLastID) // update indexing range + i.last.Store(newLastID) // update indexing range // The index limit is extended by one, update the limit without // interrupting the current background process. - if signal.newLastID == lastID+1 { - lastID = signal.newLastID + if newLastID == lastID+1 { + lastID = newLastID signal.result <- nil log.Debug("Extended state history range", "last", lastID) continue @@ -425,7 +426,9 @@ func (i *indexIniter) run(lastID uint64) { return } // Adjust the indexing target and relaunch the process - lastID = signal.newLastID + lastID = newLastID + signal.result <- nil + done, interrupt = make(chan struct{}), new(atomic.Int32) go i.index(done, interrupt, lastID) log.Debug("Shortened state history range", "last", lastID) diff --git a/triedb/pathdb/history_indexer_test.go b/triedb/pathdb/history_indexer_test.go new file mode 100644 index 00000000000..abfcafc9454 --- /dev/null +++ b/triedb/pathdb/history_indexer_test.go @@ -0,0 +1,57 @@ +// Copyright 2025 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 pathdb + +import ( + "testing" + "time" + + "github.com/ethereum/go-ethereum/core/rawdb" +) + +// TestHistoryIndexerShortenDeadlock tests that a call to shorten does not +// deadlock when the indexer is active. This specifically targets the case where +// signal.result must be sent to unblock the caller. +func TestHistoryIndexerShortenDeadlock(t *testing.T) { + //log.SetDefault(log.NewLogger(log.NewTerminalHandlerWithLevel(os.Stderr, log.LevelInfo, true))) + db := rawdb.NewMemoryDatabase() + freezer, _ := rawdb.NewStateFreezer(t.TempDir(), false, false) + defer freezer.Close() + + histories := makeHistories(100) + for i, h := range histories { + accountData, storageData, accountIndex, storageIndex := h.encode() + rawdb.WriteStateHistory(freezer, uint64(i+1), h.meta.encode(), accountIndex, storageIndex, accountData, storageData) + } + // As a workaround, assign a future block to keep the initer running indefinitely + indexer := newHistoryIndexer(db, freezer, 200) + defer indexer.close() + + done := make(chan error, 1) + go func() { + done <- indexer.shorten(200) + }() + + select { + case err := <-done: + if err != nil { + t.Fatalf("shorten returned an unexpected error: %v", err) + } + case <-time.After(2 * time.Second): + t.Fatal("timed out waiting for shorten to complete, potential deadlock") + } +} From b369a855fbb93aca7928f7cdf8a5774305a5c6c0 Mon Sep 17 00:00:00 2001 From: Marius van der Wijden Date: Thu, 24 Jul 2025 10:43:04 +0200 Subject: [PATCH 04/23] eth/protocols/snap: add healing and syncing metrics (#32258) Adds the heal time and snap sync time to grafana --------- Co-authored-by: Gary Rong --- eth/protocols/snap/metrics.go | 3 +++ eth/protocols/snap/sync.go | 26 +++++++++++++++++++++++--- 2 files changed, 26 insertions(+), 3 deletions(-) diff --git a/eth/protocols/snap/metrics.go b/eth/protocols/snap/metrics.go index 6878e5b2805..6319a9b75de 100644 --- a/eth/protocols/snap/metrics.go +++ b/eth/protocols/snap/metrics.go @@ -66,4 +66,7 @@ var ( // discarded during the snap sync. largeStorageDiscardGauge = metrics.NewRegisteredGauge("eth/protocols/snap/sync/storage/chunk/discard", nil) largeStorageResumedGauge = metrics.NewRegisteredGauge("eth/protocols/snap/sync/storage/chunk/resume", nil) + + stateSyncTimeGauge = metrics.NewRegisteredGauge("eth/protocols/snap/sync/time/statesync", nil) + stateHealTimeGauge = metrics.NewRegisteredGauge("eth/protocols/snap/sync/time/stateheal", nil) ) diff --git a/eth/protocols/snap/sync.go b/eth/protocols/snap/sync.go index 84ceb9105ea..cf4e4946453 100644 --- a/eth/protocols/snap/sync.go +++ b/eth/protocols/snap/sync.go @@ -502,8 +502,10 @@ type Syncer struct { storageHealed uint64 // Number of storage slots downloaded during the healing stage storageHealedBytes common.StorageSize // Number of raw storage bytes persisted to disk during the healing stage - startTime time.Time // Time instance when snapshot sync started - logTime time.Time // Time instance when status was last reported + startTime time.Time // Time instance when snapshot sync started + healStartTime time.Time // Time instance when the state healing started + syncTimeOnce sync.Once // Ensure that the state sync time is uploaded only once + logTime time.Time // Time instance when status was last reported pend sync.WaitGroup // Tracks network request goroutines for graceful shutdown lock sync.RWMutex // Protects fields that can change outside of sync (peers, reqs, root) @@ -685,6 +687,14 @@ func (s *Syncer) Sync(root common.Hash, cancel chan struct{}) error { s.cleanStorageTasks() s.cleanAccountTasks() if len(s.tasks) == 0 && s.healer.scheduler.Pending() == 0 { + // State healing phase completed, record the elapsed time in metrics. + // Note: healing may be rerun in subsequent cycles to fill gaps between + // pivot states (e.g., if chain sync takes longer). + if !s.healStartTime.IsZero() { + stateHealTimeGauge.Inc(int64(time.Since(s.healStartTime))) + log.Info("State healing phase is completed", "elapsed", common.PrettyDuration(time.Since(s.healStartTime))) + s.healStartTime = time.Time{} + } return nil } // Assign all the data retrieval tasks to any free peers @@ -693,7 +703,17 @@ func (s *Syncer) Sync(root common.Hash, cancel chan struct{}) error { s.assignStorageTasks(storageResps, storageReqFails, cancel) if len(s.tasks) == 0 { - // Sync phase done, run heal phase + // State sync phase completed, record the elapsed time in metrics. + // Note: the initial state sync runs only once, regardless of whether + // a new cycle is started later. Any state differences in subsequent + // cycles will be handled by the state healer. + s.syncTimeOnce.Do(func() { + stateSyncTimeGauge.Update(int64(time.Since(s.startTime))) + log.Info("State sync phase is completed", "elapsed", common.PrettyDuration(time.Since(s.startTime))) + }) + if s.healStartTime.IsZero() { + s.healStartTime = time.Now() + } s.assignTrienodeHealTasks(trienodeHealResps, trienodeHealReqFails, cancel) s.assignBytecodeHealTasks(bytecodeHealResps, bytecodeHealReqFails, cancel) } From 29eebb5eac09adddca39dbf06d66dd321b69e0a7 Mon Sep 17 00:00:00 2001 From: nthumann Date: Mon, 28 Jul 2025 02:13:50 +0100 Subject: [PATCH 05/23] core: replace the empty fmt.Errorf with errors.New (#32274) The `errors.new` function does not require string formatting, so its performance is better than that of `fmt.Errorf`. --- core/blockchain.go | 8 ++++---- core/vm/contracts.go | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/core/blockchain.go b/core/blockchain.go index d52990ec5ad..0b92a94b6c6 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -682,7 +682,7 @@ func (bc *BlockChain) initializeHistoryPruning(latest uint64) error { predefinedPoint := history.PrunePoints[bc.genesisBlock.Hash()] if predefinedPoint == nil || freezerTail != predefinedPoint.BlockNumber { log.Error("Chain history database is pruned with unknown configuration", "tail", freezerTail) - return fmt.Errorf("unexpected database tail") + return errors.New("unexpected database tail") } bc.historyPrunePoint.Store(predefinedPoint) return nil @@ -695,15 +695,15 @@ func (bc *BlockChain) initializeHistoryPruning(latest uint64) error { // action to happen. So just tell them how to do it. log.Error(fmt.Sprintf("Chain history mode is configured as %q, but database is not pruned.", bc.cfg.ChainHistoryMode.String())) log.Error(fmt.Sprintf("Run 'geth prune-history' to prune pre-merge history.")) - return fmt.Errorf("history pruning requested via configuration") + return errors.New("history pruning requested via configuration") } predefinedPoint := history.PrunePoints[bc.genesisBlock.Hash()] if predefinedPoint == nil { log.Error("Chain history pruning is not supported for this network", "genesis", bc.genesisBlock.Hash()) - return fmt.Errorf("history pruning requested for unknown network") + return errors.New("history pruning requested for unknown network") } else if freezerTail > 0 && freezerTail != predefinedPoint.BlockNumber { log.Error("Chain history database is pruned to unknown block", "tail", freezerTail) - return fmt.Errorf("unexpected database tail") + return errors.New("unexpected database tail") } bc.historyPrunePoint.Store(predefinedPoint) return nil diff --git a/core/vm/contracts.go b/core/vm/contracts.go index b65dff602ca..21307ff5ace 100644 --- a/core/vm/contracts.go +++ b/core/vm/contracts.go @@ -515,7 +515,7 @@ func (c *bigModExp) Run(input []byte) ([]byte, error) { } // enforce size cap for inputs if c.eip7823 && max(baseLen, expLen, modLen) > 1024 { - return nil, fmt.Errorf("one or more of base/exponent/modulus length exceeded 1024 bytes") + return nil, errors.New("one or more of base/exponent/modulus length exceeded 1024 bytes") } // Retrieve the operands and execute the exponentiation var ( From 0fe1bc071727504beb288ee665eb6efbb01fc5c1 Mon Sep 17 00:00:00 2001 From: Galoretka Date: Mon, 28 Jul 2025 04:16:47 +0300 Subject: [PATCH 06/23] eth/catalyst: fix error message in ExecuteStatelessPayloadV4 (#32269) Correct the error message in the ExecuteStatelessPayloadV4 function to reference newPayloadV4 and the Prague fork, instead of incorrectly referencing newPayloadV3 and Cancun. This improves clarity during debugging and aligns the error message with the actual function and fork being validated. No logic is changed. --------- Co-authored-by: rjl493456442 --- eth/catalyst/witness.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/eth/catalyst/witness.go b/eth/catalyst/witness.go index 712539c5e35..703f1b0881b 100644 --- a/eth/catalyst/witness.go +++ b/eth/catalyst/witness.go @@ -228,8 +228,8 @@ func (api *ConsensusAPI) ExecuteStatelessPayloadV4(params engine.ExecutableData, return engine.StatelessPayloadStatusV1{Status: engine.INVALID}, paramsErr("nil beaconRoot post-cancun") case executionRequests == nil: return engine.StatelessPayloadStatusV1{Status: engine.INVALID}, paramsErr("nil executionRequests post-prague") - case !api.checkFork(params.Timestamp, forks.Prague): - return engine.StatelessPayloadStatusV1{Status: engine.INVALID}, unsupportedForkErr("newPayloadV3 must only be called for cancun payloads") + case !api.checkFork(params.Timestamp, forks.Prague, forks.Osaka): + return engine.StatelessPayloadStatusV1{Status: engine.INVALID}, unsupportedForkErr("newPayloadV4 must only be called for prague payloads") } requests := convertRequests(executionRequests) if err := validateRequests(requests); err != nil { From a7aed7bd6f6504b49854a445f27b9cfb4a94df51 Mon Sep 17 00:00:00 2001 From: rjl493456442 Date: Mon, 28 Jul 2025 14:57:45 +0800 Subject: [PATCH 07/23] cmd, eth, internal: introduce debug_sync (#32177) Alternative implementation of https://github.com/ethereum/go-ethereum/pull/32159 --- cmd/geth/config.go | 6 +- cmd/utils/flags.go | 14 ++- eth/catalyst/tester.go | 103 ----------------- eth/downloader/beacondevsync.go | 23 +--- eth/downloader/fetchers.go | 3 - eth/syncer/syncer.go | 197 ++++++++++++++++++++++++++++++++ internal/web3ext/web3ext.go | 5 + 7 files changed, 219 insertions(+), 132 deletions(-) delete mode 100644 eth/catalyst/tester.go create mode 100644 eth/syncer/syncer.go diff --git a/cmd/geth/config.go b/cmd/geth/config.go index d7c354ff9f2..96bd715e889 100644 --- a/cmd/geth/config.go +++ b/cmd/geth/config.go @@ -262,14 +262,16 @@ func makeFullNode(ctx *cli.Context) *node.Node { if cfg.Ethstats.URL != "" { utils.RegisterEthStatsService(stack, backend, cfg.Ethstats.URL) } - // Configure full-sync tester service if requested + // Configure synchronization override service + var synctarget common.Hash if ctx.IsSet(utils.SyncTargetFlag.Name) { hex := hexutil.MustDecode(ctx.String(utils.SyncTargetFlag.Name)) if len(hex) != common.HashLength { utils.Fatalf("invalid sync target length: have %d, want %d", len(hex), common.HashLength) } - utils.RegisterFullSyncTester(stack, eth, common.BytesToHash(hex), ctx.Bool(utils.ExitWhenSyncedFlag.Name)) + synctarget = common.BytesToHash(hex) } + utils.RegisterSyncOverrideService(stack, eth, synctarget, ctx.Bool(utils.ExitWhenSyncedFlag.Name)) if ctx.IsSet(utils.DeveloperFlag.Name) { // Start dev mode. diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index b86970651f4..cbc1d925e4a 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -49,10 +49,10 @@ import ( "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto/kzg4844" "github.com/ethereum/go-ethereum/eth" - "github.com/ethereum/go-ethereum/eth/catalyst" "github.com/ethereum/go-ethereum/eth/ethconfig" "github.com/ethereum/go-ethereum/eth/filters" "github.com/ethereum/go-ethereum/eth/gasprice" + "github.com/ethereum/go-ethereum/eth/syncer" "github.com/ethereum/go-ethereum/eth/tracers" "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/ethdb/remotedb" @@ -1997,10 +1997,14 @@ func RegisterFilterAPI(stack *node.Node, backend ethapi.Backend, ethcfg *ethconf return filterSystem } -// RegisterFullSyncTester adds the full-sync tester service into node. -func RegisterFullSyncTester(stack *node.Node, eth *eth.Ethereum, target common.Hash, exitWhenSynced bool) { - catalyst.RegisterFullSyncTester(stack, eth, target, exitWhenSynced) - log.Info("Registered full-sync tester", "hash", target, "exitWhenSynced", exitWhenSynced) +// RegisterSyncOverrideService adds the synchronization override service into node. +func RegisterSyncOverrideService(stack *node.Node, eth *eth.Ethereum, target common.Hash, exitWhenSynced bool) { + if target != (common.Hash{}) { + log.Info("Registered sync override service", "hash", target, "exitWhenSynced", exitWhenSynced) + } else { + log.Info("Registered sync override service") + } + syncer.Register(stack, eth, target, exitWhenSynced) } // SetupMetrics configures the metrics system. diff --git a/eth/catalyst/tester.go b/eth/catalyst/tester.go deleted file mode 100644 index 10a480837e2..00000000000 --- a/eth/catalyst/tester.go +++ /dev/null @@ -1,103 +0,0 @@ -// Copyright 2022 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 catalyst - -import ( - "sync" - "time" - - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/eth" - "github.com/ethereum/go-ethereum/eth/ethconfig" - "github.com/ethereum/go-ethereum/log" - "github.com/ethereum/go-ethereum/node" -) - -// FullSyncTester is an auxiliary service that allows Geth to perform full sync -// alone without consensus-layer attached. Users must specify a valid block hash -// as the sync target. -// -// This tester can be applied to different networks, no matter it's pre-merge or -// post-merge, but only for full-sync. -type FullSyncTester struct { - stack *node.Node - backend *eth.Ethereum - target common.Hash - closed chan struct{} - wg sync.WaitGroup - exitWhenSynced bool -} - -// RegisterFullSyncTester registers the full-sync tester service into the node -// stack for launching and stopping the service controlled by node. -func RegisterFullSyncTester(stack *node.Node, backend *eth.Ethereum, target common.Hash, exitWhenSynced bool) (*FullSyncTester, error) { - cl := &FullSyncTester{ - stack: stack, - backend: backend, - target: target, - closed: make(chan struct{}), - exitWhenSynced: exitWhenSynced, - } - stack.RegisterLifecycle(cl) - return cl, nil -} - -// Start launches the beacon sync with provided sync target. -func (tester *FullSyncTester) Start() error { - tester.wg.Add(1) - go func() { - defer tester.wg.Done() - - // Trigger beacon sync with the provided block hash as trusted - // chain head. - err := tester.backend.Downloader().BeaconDevSync(ethconfig.FullSync, tester.target, tester.closed) - if err != nil { - log.Info("Failed to trigger beacon sync", "err", err) - } - - ticker := time.NewTicker(time.Second * 5) - defer ticker.Stop() - - for { - select { - case <-ticker.C: - // Stop in case the target block is already stored locally. - if block := tester.backend.BlockChain().GetBlockByHash(tester.target); block != nil { - log.Info("Full-sync target reached", "number", block.NumberU64(), "hash", block.Hash()) - - if tester.exitWhenSynced { - go tester.stack.Close() // async since we need to close ourselves - log.Info("Terminating the node") - } - return - } - - case <-tester.closed: - return - } - } - }() - return nil -} - -// Stop stops the full-sync tester to stop all background activities. -// This function can only be called for one time. -func (tester *FullSyncTester) Stop() error { - close(tester.closed) - tester.wg.Wait() - return nil -} diff --git a/eth/downloader/beacondevsync.go b/eth/downloader/beacondevsync.go index 0032eb53b96..7b306841337 100644 --- a/eth/downloader/beacondevsync.go +++ b/eth/downloader/beacondevsync.go @@ -18,7 +18,6 @@ package downloader import ( "errors" - "time" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" @@ -34,28 +33,14 @@ import ( // Note, this must not be used in live code. If the forkchcoice endpoint where // to use this instead of giving us the payload first, then essentially nobody // in the network would have the block yet that we'd attempt to retrieve. -func (d *Downloader) BeaconDevSync(mode SyncMode, hash common.Hash, stop chan struct{}) error { +func (d *Downloader) BeaconDevSync(mode SyncMode, header *types.Header) error { // Be very loud that this code should not be used in a live node log.Warn("----------------------------------") - log.Warn("Beacon syncing with hash as target", "hash", hash) + log.Warn("Beacon syncing with hash as target", "number", header.Number, "hash", header.Hash()) log.Warn("This is unhealthy for a live node!") + log.Warn("This is incompatible with the consensus layer!") log.Warn("----------------------------------") - - log.Info("Waiting for peers to retrieve sync target") - for { - // If the node is going down, unblock - select { - case <-stop: - return errors.New("stop requested") - default: - } - header, err := d.GetHeader(hash) - if err != nil { - time.Sleep(time.Second) - continue - } - return d.BeaconSync(mode, header, header) - } + return d.BeaconSync(mode, header, header) } // GetHeader tries to retrieve the header with a given hash from a random peer. diff --git a/eth/downloader/fetchers.go b/eth/downloader/fetchers.go index 4ebb9bbc98a..6e5c65eb207 100644 --- a/eth/downloader/fetchers.go +++ b/eth/downloader/fetchers.go @@ -45,9 +45,6 @@ func (d *Downloader) fetchHeadersByHash(p *peerConnection, hash common.Hash, amo defer timeoutTimer.Stop() select { - case <-d.cancelCh: - return nil, nil, errCanceled - case <-timeoutTimer.C: // Header retrieval timed out, update the metrics p.log.Debug("Header request timed out", "elapsed", ttl) diff --git a/eth/syncer/syncer.go b/eth/syncer/syncer.go new file mode 100644 index 00000000000..5c4d2401e9f --- /dev/null +++ b/eth/syncer/syncer.go @@ -0,0 +1,197 @@ +// Copyright 2025 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 syncer + +import ( + "errors" + "fmt" + "sync" + "time" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/eth" + "github.com/ethereum/go-ethereum/eth/ethconfig" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/node" + "github.com/ethereum/go-ethereum/rpc" +) + +type syncReq struct { + hash common.Hash + errc chan error +} + +// Syncer is an auxiliary service that allows Geth to perform full sync +// alone without consensus-layer attached. Users must specify a valid block hash +// as the sync target. +// +// This tool can be applied to different networks, no matter it's pre-merge or +// post-merge, but only for full-sync. +type Syncer struct { + stack *node.Node + backend *eth.Ethereum + target common.Hash + request chan *syncReq + closed chan struct{} + wg sync.WaitGroup + exitWhenSynced bool +} + +// Register registers the synchronization override service into the node +// stack for launching and stopping the service controlled by node. +func Register(stack *node.Node, backend *eth.Ethereum, target common.Hash, exitWhenSynced bool) (*Syncer, error) { + s := &Syncer{ + stack: stack, + backend: backend, + target: target, + request: make(chan *syncReq), + closed: make(chan struct{}), + exitWhenSynced: exitWhenSynced, + } + stack.RegisterAPIs(s.APIs()) + stack.RegisterLifecycle(s) + return s, nil +} + +// APIs return the collection of RPC services the ethereum package offers. +// NOTE, some of these services probably need to be moved to somewhere else. +func (s *Syncer) APIs() []rpc.API { + return []rpc.API{ + { + Namespace: "debug", + Service: NewAPI(s), + }, + } +} + +// run is the main loop that monitors sync requests from users and initiates +// sync operations when necessary. It also checks whether the specified target +// has been reached and shuts down Geth if requested by the user. +func (s *Syncer) run() { + defer s.wg.Done() + + var ( + target *types.Header + ticker = time.NewTicker(time.Second * 5) + ) + for { + select { + case req := <-s.request: + var ( + resync bool + retries int + logged bool + ) + for { + if retries >= 10 { + req.errc <- fmt.Errorf("sync target is not avaibale, %x", req.hash) + break + } + select { + case <-s.closed: + req.errc <- errors.New("syncer closed") + return + default: + } + + header, err := s.backend.Downloader().GetHeader(req.hash) + if err != nil { + if !logged { + logged = true + log.Info("Waiting for peers to retrieve sync target", "hash", req.hash) + } + time.Sleep(time.Second * time.Duration(retries+1)) + retries++ + continue + } + if target != nil && header.Number.Cmp(target.Number) <= 0 { + req.errc <- fmt.Errorf("stale sync target, current: %d, received: %d", target.Number, header.Number) + break + } + target = header + resync = true + break + } + if resync { + req.errc <- s.backend.Downloader().BeaconDevSync(ethconfig.FullSync, target) + } + + case <-ticker.C: + if target == nil || !s.exitWhenSynced { + continue + } + if block := s.backend.BlockChain().GetBlockByHash(target.Hash()); block != nil { + log.Info("Sync target reached", "number", block.NumberU64(), "hash", block.Hash()) + go s.stack.Close() // async since we need to close ourselves + return + } + + case <-s.closed: + return + } + } +} + +// Start launches the synchronization service. +func (s *Syncer) Start() error { + s.wg.Add(1) + go s.run() + if s.target == (common.Hash{}) { + return nil + } + return s.Sync(s.target) +} + +// Stop terminates the synchronization service and stop all background activities. +// This function can only be called for one time. +func (s *Syncer) Stop() error { + close(s.closed) + s.wg.Wait() + return nil +} + +// Sync sets the synchronization target. Notably, setting a target lower than the +// previous one is not allowed, as backward synchronization is not supported. +func (s *Syncer) Sync(hash common.Hash) error { + req := &syncReq{ + hash: hash, + errc: make(chan error, 1), + } + select { + case s.request <- req: + return <-req.errc + case <-s.closed: + return errors.New("syncer is closed") + } +} + +// API is the collection of synchronization service APIs for debugging the +// protocol. +type API struct { + s *Syncer +} + +// NewAPI creates a new debug API instance. +func NewAPI(s *Syncer) *API { + return &API{s: s} +} + +// Sync initiates a full sync to the target block hash. +func (api *API) Sync(target common.Hash) error { + return api.s.Sync(target) +} diff --git a/internal/web3ext/web3ext.go b/internal/web3ext/web3ext.go index a6d93fc1c53..d7f37a79eea 100644 --- a/internal/web3ext/web3ext.go +++ b/internal/web3ext/web3ext.go @@ -468,6 +468,11 @@ web3._extend({ call: 'debug_getTrieFlushInterval', params: 0 }), + new web3._extend.Method({ + name: 'sync', + call: 'debug_sync', + params: 1 + }), ], properties: [] }); From 32d537cd588efe31e70ad3333cdaaed35f041a21 Mon Sep 17 00:00:00 2001 From: ericxtheodore Date: Mon, 28 Jul 2025 16:13:18 +0800 Subject: [PATCH 08/23] all: replace fmt.Errorf with errors.New (#32286) The errors.new function does not require string formatting, so its performance is better than that of fmt.Errorf. --- cmd/devp2p/internal/ethtest/conn.go | 2 +- cmd/devp2p/internal/ethtest/suite.go | 7 ++++--- cmd/geth/chaincmd.go | 2 +- cmd/workload/testsuite.go | 6 +++--- core/state/snapshot/journal.go | 2 +- core/tracing/journal.go | 6 +++--- core/txpool/validation.go | 2 +- core/types/bal/bal_encoding.go | 2 +- eth/catalyst/api_test.go | 2 +- eth/ethconfig/config.go | 4 ++-- ethdb/leveldb/leveldb.go | 7 ++++--- ethdb/memorydb/memorydb.go | 3 +-- ethdb/pebble/pebble.go | 3 ++- signer/core/apitypes/types.go | 2 +- tests/transaction_test_util.go | 7 ++++--- triedb/pathdb/history_index.go | 2 +- 16 files changed, 31 insertions(+), 28 deletions(-) diff --git a/cmd/devp2p/internal/ethtest/conn.go b/cmd/devp2p/internal/ethtest/conn.go index 4a7a2c76d8f..5182d71ce19 100644 --- a/cmd/devp2p/internal/ethtest/conn.go +++ b/cmd/devp2p/internal/ethtest/conn.go @@ -129,7 +129,7 @@ func (c *Conn) Write(proto Proto, code uint64, msg any) error { return err } -var errDisc error = fmt.Errorf("disconnect") +var errDisc error = errors.New("disconnect") // ReadEth reads an Eth sub-protocol wire message. func (c *Conn) ReadEth() (any, error) { diff --git a/cmd/devp2p/internal/ethtest/suite.go b/cmd/devp2p/internal/ethtest/suite.go index b5a346c0741..47d00761f32 100644 --- a/cmd/devp2p/internal/ethtest/suite.go +++ b/cmd/devp2p/internal/ethtest/suite.go @@ -19,6 +19,7 @@ package ethtest import ( "context" "crypto/rand" + "errors" "fmt" "reflect" "sync" @@ -1092,7 +1093,7 @@ func (s *Suite) testBadBlobTx(t *utesting.T, tx *types.Transaction, badTx *types return } if !readUntilDisconnect(conn) { - errc <- fmt.Errorf("expected bad peer to be disconnected") + errc <- errors.New("expected bad peer to be disconnected") return } stage3.Done() @@ -1139,7 +1140,7 @@ func (s *Suite) testBadBlobTx(t *utesting.T, tx *types.Transaction, badTx *types } if req.GetPooledTransactionsRequest[0] != tx.Hash() { - errc <- fmt.Errorf("requested unknown tx hash") + errc <- errors.New("requested unknown tx hash") return } @@ -1149,7 +1150,7 @@ func (s *Suite) testBadBlobTx(t *utesting.T, tx *types.Transaction, badTx *types return } if readUntilDisconnect(conn) { - errc <- fmt.Errorf("unexpected disconnect") + errc <- errors.New("unexpected disconnect") return } close(errc) diff --git a/cmd/geth/chaincmd.go b/cmd/geth/chaincmd.go index 44e11dbf06b..112d1a539b4 100644 --- a/cmd/geth/chaincmd.go +++ b/cmd/geth/chaincmd.go @@ -716,7 +716,7 @@ func downloadEra(ctx *cli.Context) error { case ctx.IsSet(utils.SepoliaFlag.Name): network = "sepolia" default: - return fmt.Errorf("unsupported network, no known era1 checksums") + return errors.New("unsupported network, no known era1 checksums") } } diff --git a/cmd/workload/testsuite.go b/cmd/workload/testsuite.go index 39eeb8e3c20..25dc17a49e7 100644 --- a/cmd/workload/testsuite.go +++ b/cmd/workload/testsuite.go @@ -18,7 +18,7 @@ package main import ( "embed" - "fmt" + "errors" "io/fs" "os" @@ -97,7 +97,7 @@ type testConfig struct { traceTestFile string } -var errPrunedHistory = fmt.Errorf("attempt to access pruned history") +var errPrunedHistory = errors.New("attempt to access pruned history") // validateHistoryPruneErr checks whether the given error is caused by access // to history before the pruning threshold block (it is an rpc.Error with code 4444). @@ -109,7 +109,7 @@ func validateHistoryPruneErr(err error, blockNum uint64, historyPruneBlock *uint if err != nil { if rpcErr, ok := err.(rpc.Error); ok && rpcErr.ErrorCode() == 4444 { if historyPruneBlock != nil && blockNum > *historyPruneBlock { - return fmt.Errorf("pruned history error returned after pruning threshold") + return errors.New("pruned history error returned after pruning threshold") } return errPrunedHistory } diff --git a/core/state/snapshot/journal.go b/core/state/snapshot/journal.go index e4b396b9903..004dd5298ac 100644 --- a/core/state/snapshot/journal.go +++ b/core/state/snapshot/journal.go @@ -350,7 +350,7 @@ func iterateJournal(db ethdb.KeyValueReader, callback journalCallback) error { } if len(destructs) > 0 { log.Warn("Incompatible legacy journal detected", "version", journalV0) - return fmt.Errorf("incompatible legacy journal detected") + return errors.New("incompatible legacy journal detected") } } if err := r.Decode(&accounts); err != nil { diff --git a/core/tracing/journal.go b/core/tracing/journal.go index 8937d4c5ae2..a402f1ac098 100644 --- a/core/tracing/journal.go +++ b/core/tracing/journal.go @@ -17,7 +17,7 @@ package tracing import ( - "fmt" + "errors" "math/big" "github.com/ethereum/go-ethereum/common" @@ -39,14 +39,14 @@ type entry interface { // WrapWithJournal wraps the given tracer with a journaling layer. func WrapWithJournal(hooks *Hooks) (*Hooks, error) { if hooks == nil { - return nil, fmt.Errorf("wrapping nil tracer") + return nil, errors.New("wrapping nil tracer") } // No state change to journal, return the wrapped hooks as is if hooks.OnBalanceChange == nil && hooks.OnNonceChange == nil && hooks.OnNonceChangeV2 == nil && hooks.OnCodeChange == nil && hooks.OnStorageChange == nil { return hooks, nil } if hooks.OnNonceChange != nil && hooks.OnNonceChangeV2 != nil { - return nil, fmt.Errorf("cannot have both OnNonceChange and OnNonceChangeV2") + return nil, errors.New("cannot have both OnNonceChange and OnNonceChangeV2") } // Create a new Hooks instance and copy all hooks diff --git a/core/txpool/validation.go b/core/txpool/validation.go index 80ba994d1a4..d4f34010866 100644 --- a/core/txpool/validation.go +++ b/core/txpool/validation.go @@ -145,7 +145,7 @@ func ValidateTransaction(tx *types.Transaction, head *types.Header, signer types } if tx.Type() == types.SetCodeTxType { if len(tx.SetCodeAuthorizations()) == 0 { - return fmt.Errorf("set code tx must have at least one authorization tuple") + return errors.New("set code tx must have at least one authorization tuple") } } return nil diff --git a/core/types/bal/bal_encoding.go b/core/types/bal/bal_encoding.go index d7d08801b11..24dfafa0831 100644 --- a/core/types/bal/bal_encoding.go +++ b/core/types/bal/bal_encoding.go @@ -169,7 +169,7 @@ func (e *AccountAccess) validate() error { // Convert code change if len(e.Code) == 1 { if len(e.Code[0].Code) > params.MaxCodeSize { - return fmt.Errorf("code change contained oversized code") + return errors.New("code change contained oversized code") } } return nil diff --git a/eth/catalyst/api_test.go b/eth/catalyst/api_test.go index dc7967ba2e6..ad377113b57 100644 --- a/eth/catalyst/api_test.go +++ b/eth/catalyst/api_test.go @@ -1497,7 +1497,7 @@ func checkEqualBody(a *types.Body, b *engine.ExecutionPayloadBody) error { } } if !reflect.DeepEqual(a.Withdrawals, b.Withdrawals) { - return fmt.Errorf("withdrawals mismatch") + return errors.New("withdrawals mismatch") } return nil } diff --git a/eth/ethconfig/config.go b/eth/ethconfig/config.go index 97d23744a0a..7eba6a6408b 100644 --- a/eth/ethconfig/config.go +++ b/eth/ethconfig/config.go @@ -18,7 +18,7 @@ package ethconfig import ( - "fmt" + "errors" "time" "github.com/ethereum/go-ethereum/common" @@ -171,7 +171,7 @@ type Config struct { func CreateConsensusEngine(config *params.ChainConfig, db ethdb.Database) (consensus.Engine, error) { if config.TerminalTotalDifficulty == nil { log.Error("Geth only supports PoS networks. Please transition legacy networks using Geth v1.13.x.") - return nil, fmt.Errorf("'terminalTotalDifficulty' is not set in genesis block") + return nil, errors.New("'terminalTotalDifficulty' is not set in genesis block") } // Wrap previously supported consensus engines into their post-merge counterpart if config.Clique != nil { diff --git a/ethdb/leveldb/leveldb.go b/ethdb/leveldb/leveldb.go index 736a44d73d6..8e1bb86fec4 100644 --- a/ethdb/leveldb/leveldb.go +++ b/ethdb/leveldb/leveldb.go @@ -22,6 +22,7 @@ package leveldb import ( "bytes" + "errors" "fmt" "sync" "time" @@ -31,7 +32,7 @@ import ( "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/metrics" "github.com/syndtr/goleveldb/leveldb" - "github.com/syndtr/goleveldb/leveldb/errors" + lerrors "github.com/syndtr/goleveldb/leveldb/errors" "github.com/syndtr/goleveldb/leveldb/filter" "github.com/syndtr/goleveldb/leveldb/opt" "github.com/syndtr/goleveldb/leveldb/util" @@ -120,7 +121,7 @@ func NewCustom(file string, namespace string, customize func(options *opt.Option // Open the db and recover any potential corruptions db, err := leveldb.OpenFile(file, options) - if _, corrupted := err.(*errors.ErrCorrupted); corrupted { + if _, corrupted := err.(*lerrors.ErrCorrupted); corrupted { db, err = leveldb.RecoverFile(file, nil) } if err != nil { @@ -548,7 +549,7 @@ func (r *replayer) DeleteRange(start, end []byte) { if rangeDeleter, ok := r.writer.(ethdb.KeyValueRangeDeleter); ok { r.failure = rangeDeleter.DeleteRange(start, end) } else { - r.failure = fmt.Errorf("ethdb.KeyValueWriter does not implement DeleteRange") + r.failure = errors.New("ethdb.KeyValueWriter does not implement DeleteRange") } } diff --git a/ethdb/memorydb/memorydb.go b/ethdb/memorydb/memorydb.go index 5c4c48de649..200ad602456 100644 --- a/ethdb/memorydb/memorydb.go +++ b/ethdb/memorydb/memorydb.go @@ -20,7 +20,6 @@ package memorydb import ( "bytes" "errors" - "fmt" "sort" "strings" "sync" @@ -327,7 +326,7 @@ func (b *batch) Replay(w ethdb.KeyValueWriter) error { return err } } else { - return fmt.Errorf("ethdb.KeyValueWriter does not implement DeleteRange") + return errors.New("ethdb.KeyValueWriter does not implement DeleteRange") } } continue diff --git a/ethdb/pebble/pebble.go b/ethdb/pebble/pebble.go index 58a521f6fb6..2370d4654f3 100644 --- a/ethdb/pebble/pebble.go +++ b/ethdb/pebble/pebble.go @@ -18,6 +18,7 @@ package pebble import ( + "errors" "fmt" "runtime" "strings" @@ -705,7 +706,7 @@ func (b *batch) Replay(w ethdb.KeyValueWriter) error { return err } } else { - return fmt.Errorf("ethdb.KeyValueWriter does not implement DeleteRange") + return errors.New("ethdb.KeyValueWriter does not implement DeleteRange") } } else { return fmt.Errorf("unhandled operation, keytype: %v", kind) diff --git a/signer/core/apitypes/types.go b/signer/core/apitypes/types.go index 66c750a9c30..b5fd5a28540 100644 --- a/signer/core/apitypes/types.go +++ b/signer/core/apitypes/types.go @@ -151,7 +151,7 @@ func (args *SendTxArgs) ToTransaction() (*types.Transaction, error) { al = *args.AccessList } if to == nil { - return nil, fmt.Errorf("transaction recipient must be set for blob transactions") + return nil, errors.New("transaction recipient must be set for blob transactions") } data = &types.BlobTx{ To: *to, diff --git a/tests/transaction_test_util.go b/tests/transaction_test_util.go index b2efabe82e0..a90c2d522f6 100644 --- a/tests/transaction_test_util.go +++ b/tests/transaction_test_util.go @@ -17,6 +17,7 @@ package tests import ( + "errors" "fmt" "math/big" @@ -43,7 +44,7 @@ type ttFork struct { func (tt *TransactionTest) validate() error { if tt.Txbytes == nil { - return fmt.Errorf("missing txbytes") + return errors.New("missing txbytes") } for name, fork := range tt.Result { if err := tt.validateFork(fork); err != nil { @@ -58,10 +59,10 @@ func (tt *TransactionTest) validateFork(fork *ttFork) error { return nil } if fork.Hash == nil && fork.Exception == nil { - return fmt.Errorf("missing hash and exception") + return errors.New("missing hash and exception") } if fork.Hash != nil && fork.Sender == nil { - return fmt.Errorf("missing sender") + return errors.New("missing sender") } return nil } diff --git a/triedb/pathdb/history_index.go b/triedb/pathdb/history_index.go index f79581b38b1..e781a898e1a 100644 --- a/triedb/pathdb/history_index.go +++ b/triedb/pathdb/history_index.go @@ -353,7 +353,7 @@ func (d *indexDeleter) empty() bool { // pop removes the last written element from the index writer. func (d *indexDeleter) pop(id uint64) error { if id == 0 { - return fmt.Errorf("zero history ID is not valid") + return errors.New("zero history ID is not valid") } if id != d.lastID { return fmt.Errorf("pop element out of order, last: %d, this: %d", d.lastID, id) From b64a5001635f7eefe91d9d90dfbf430a3906da7f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Andr=C3=B3il?= Date: Mon, 28 Jul 2025 14:56:29 +0200 Subject: [PATCH 09/23] downloader: fix typos, grammar and formatting (#32288) --- eth/downloader/downloader.go | 8 ++++---- eth/downloader/downloader_test.go | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/eth/downloader/downloader.go b/eth/downloader/downloader.go index dcda4e521cb..09837a30450 100644 --- a/eth/downloader/downloader.go +++ b/eth/downloader/downloader.go @@ -199,7 +199,7 @@ type BlockChain interface { // InsertChain inserts a batch of blocks into the local chain. InsertChain(types.Blocks) (int, error) - // InterruptInsert whether disables the chain insertion. + // InterruptInsert disables or enables chain insertion. InterruptInsert(on bool) // InsertReceiptChain inserts a batch of blocks along with their receipts @@ -513,7 +513,7 @@ func (d *Downloader) syncToHead() (err error) { // // For non-merged networks, if there is a checkpoint available, then calculate // the ancientLimit through that. Otherwise calculate the ancient limit through - // the advertised height of the remote peer. This most is mostly a fallback for + // the advertised height of the remote peer. This is mostly a fallback for // legacy networks, but should eventually be dropped. TODO(karalabe). // // Beacon sync, use the latest finalized block as the ancient limit @@ -946,7 +946,7 @@ func (d *Downloader) processSnapSyncContent() error { if !d.committed.Load() { latest := results[len(results)-1].Header // If the height is above the pivot block by 2 sets, it means the pivot - // become stale in the network, and it was garbage collected, move to a + // became stale in the network, and it was garbage collected, move to a // new pivot. // // Note, we have `reorgProtHeaderDelay` number of blocks withheld, Those @@ -1043,7 +1043,7 @@ func (d *Downloader) commitSnapSyncData(results []*fetchResult, stateSync *state first, last := results[0].Header, results[len(results)-1].Header log.Debug("Inserting snap-sync blocks", "items", len(results), "firstnum", first.Number, "firsthash", first.Hash(), - "lastnumn", last.Number, "lasthash", last.Hash(), + "lastnum", last.Number, "lasthash", last.Hash(), ) blocks := make([]*types.Block, len(results)) receipts := make([]rlp.RawValue, len(results)) diff --git a/eth/downloader/downloader_test.go b/eth/downloader/downloader_test.go index 669ce003cfe..c1a31d6e1c2 100644 --- a/eth/downloader/downloader_test.go +++ b/eth/downloader/downloader_test.go @@ -544,7 +544,7 @@ func testMultiProtoSync(t *testing.T, protocol uint, mode SyncMode) { tester.newPeer("peer 68", eth.ETH68, chain.blocks[1:]) if err := tester.downloader.BeaconSync(mode, chain.blocks[len(chain.blocks)-1].Header(), nil); err != nil { - t.Fatalf("failed to start beacon sync: #{err}") + t.Fatalf("failed to start beacon sync: %v", err) } select { case <-complete: From eb7aef45a73b3151b25a1174d6cddcd0cf2fee0c Mon Sep 17 00:00:00 2001 From: kashitaka Date: Mon, 28 Jul 2025 23:17:36 +0900 Subject: [PATCH 10/23] ethclient/simulated: Fix flaky rollback test (#32280) This PR addresses a flakiness in the rollback test discussed in https://github.com/ethereum/go-ethereum/issues/32252 I found `nonce` collision caused transactions occasionally fail to send. I tried to change error message in the failed test like: ``` if err = client.SendTransaction(ctx, signedTx); err != nil { t.Fatalf("failed to send transaction: %v, nonce: %d", err, signedTx.Nonce()) } ``` and I occasionally got test failure with this message: ``` === CONT TestFlakyFunction/Run_#100 rollback_test.go:44: failed to send transaction: already known, nonce: 0 --- FAIL: TestFlakyFunction/Run_#100 (0.07s) ``` Although `nonces` are obtained via `PendingNonceAt`, we observed that, in rare cases (approximately 1 in 1000), two transactions from the same sender end up with the same nonce. This likely happens because `tx0` has not yet propagated to the transaction pool before `tx1` requests its nonce. When the test succeeds, `tx0` and `tx1` have nonces `0` and `1`, respectively. However, in rare failures, both transactions end up with nonce `0`. We modified the test to explicitly assign nonces to each transaction. By controlling the nonce values manually, we eliminated the race condition and ensured consistent behavior. After several thousand runs, the flakiness was no longer reproducible in my local environment. Reduced internal polling interval in `pendingStateHasTx()` to speed up test execution without impacting stability. It reduces test time for `TestTransactionRollbackBehavior` from about 7 seconds to 2 seconds. --- ethclient/simulated/backend_test.go | 21 +++++++-------------- ethclient/simulated/rollback_test.go | 22 +++++++++++----------- 2 files changed, 18 insertions(+), 25 deletions(-) diff --git a/ethclient/simulated/backend_test.go b/ethclient/simulated/backend_test.go index 7a399d41f3d..ee20cd171ab 100644 --- a/ethclient/simulated/backend_test.go +++ b/ethclient/simulated/backend_test.go @@ -52,7 +52,7 @@ func simTestBackend(testAddr common.Address) *Backend { ) } -func newBlobTx(sim *Backend, key *ecdsa.PrivateKey) (*types.Transaction, error) { +func newBlobTx(sim *Backend, key *ecdsa.PrivateKey, nonce uint64) (*types.Transaction, error) { client := sim.Client() testBlob := &kzg4844.Blob{0x00} @@ -67,12 +67,8 @@ func newBlobTx(sim *Backend, key *ecdsa.PrivateKey) (*types.Transaction, error) addr := crypto.PubkeyToAddress(key.PublicKey) chainid, _ := client.ChainID(context.Background()) - nonce, err := client.PendingNonceAt(context.Background(), addr) - if err != nil { - return nil, err - } - chainidU256, _ := uint256.FromBig(chainid) + tx := types.NewTx(&types.BlobTx{ ChainID: chainidU256, GasTipCap: gasTipCapU256, @@ -88,7 +84,7 @@ func newBlobTx(sim *Backend, key *ecdsa.PrivateKey) (*types.Transaction, error) return types.SignTx(tx, types.LatestSignerForChainID(chainid), key) } -func newTx(sim *Backend, key *ecdsa.PrivateKey) (*types.Transaction, error) { +func newTx(sim *Backend, key *ecdsa.PrivateKey, nonce uint64) (*types.Transaction, error) { client := sim.Client() // create a signed transaction to send @@ -96,10 +92,7 @@ func newTx(sim *Backend, key *ecdsa.PrivateKey) (*types.Transaction, error) { gasPrice := new(big.Int).Add(head.BaseFee, big.NewInt(params.GWei)) addr := crypto.PubkeyToAddress(key.PublicKey) chainid, _ := client.ChainID(context.Background()) - nonce, err := client.PendingNonceAt(context.Background(), addr) - if err != nil { - return nil, err - } + tx := types.NewTx(&types.DynamicFeeTx{ ChainID: chainid, Nonce: nonce, @@ -161,7 +154,7 @@ func TestSendTransaction(t *testing.T) { client := sim.Client() ctx := context.Background() - signedTx, err := newTx(sim, testKey) + signedTx, err := newTx(sim, testKey, 0) if err != nil { t.Errorf("could not create transaction: %v", err) } @@ -252,7 +245,7 @@ func TestForkResendTx(t *testing.T) { parent, _ := client.HeaderByNumber(ctx, nil) // 2. - tx, err := newTx(sim, testKey) + tx, err := newTx(sim, testKey, 0) if err != nil { t.Fatalf("could not create transaction: %v", err) } @@ -297,7 +290,7 @@ func TestCommitReturnValue(t *testing.T) { } // Create a block in the original chain (containing a transaction to force different block hashes) - tx, _ := newTx(sim, testKey) + tx, _ := newTx(sim, testKey, 0) if err := client.SendTransaction(ctx, tx); err != nil { t.Errorf("sending transaction: %v", err) } diff --git a/ethclient/simulated/rollback_test.go b/ethclient/simulated/rollback_test.go index 57c59496d5b..093467d2910 100644 --- a/ethclient/simulated/rollback_test.go +++ b/ethclient/simulated/rollback_test.go @@ -38,9 +38,9 @@ func TestTransactionRollbackBehavior(t *testing.T) { defer sim.Close() client := sim.Client() - btx0 := testSendSignedTx(t, testKey, sim, true) - tx0 := testSendSignedTx(t, testKey2, sim, false) - tx1 := testSendSignedTx(t, testKey2, sim, false) + btx0 := testSendSignedTx(t, testKey, sim, true, 0) + tx0 := testSendSignedTx(t, testKey2, sim, false, 0) + tx1 := testSendSignedTx(t, testKey2, sim, false, 1) sim.Rollback() @@ -48,9 +48,9 @@ func TestTransactionRollbackBehavior(t *testing.T) { t.Fatalf("all transactions were not rolled back") } - btx2 := testSendSignedTx(t, testKey, sim, true) - tx2 := testSendSignedTx(t, testKey2, sim, false) - tx3 := testSendSignedTx(t, testKey2, sim, false) + btx2 := testSendSignedTx(t, testKey, sim, true, 0) + tx2 := testSendSignedTx(t, testKey2, sim, false, 0) + tx3 := testSendSignedTx(t, testKey2, sim, false, 1) sim.Commit() @@ -61,7 +61,7 @@ func TestTransactionRollbackBehavior(t *testing.T) { // testSendSignedTx sends a signed transaction to the simulated backend. // It does not commit the block. -func testSendSignedTx(t *testing.T, key *ecdsa.PrivateKey, sim *Backend, isBlobTx bool) *types.Transaction { +func testSendSignedTx(t *testing.T, key *ecdsa.PrivateKey, sim *Backend, isBlobTx bool, nonce uint64) *types.Transaction { t.Helper() client := sim.Client() ctx := context.Background() @@ -71,9 +71,9 @@ func testSendSignedTx(t *testing.T, key *ecdsa.PrivateKey, sim *Backend, isBlobT signedTx *types.Transaction ) if isBlobTx { - signedTx, err = newBlobTx(sim, key) + signedTx, err = newBlobTx(sim, key, nonce) } else { - signedTx, err = newTx(sim, key) + signedTx, err = newTx(sim, key, nonce) } if err != nil { t.Fatalf("failed to create transaction: %v", err) @@ -96,13 +96,13 @@ func pendingStateHasTx(client Client, tx *types.Transaction) bool { ) // Poll for receipt with timeout - deadline := time.Now().Add(2 * time.Second) + deadline := time.Now().Add(200 * time.Millisecond) for time.Now().Before(deadline) { receipt, err = client.TransactionReceipt(ctx, tx.Hash()) if err == nil && receipt != nil { break } - time.Sleep(100 * time.Millisecond) + time.Sleep(5 * time.Millisecond) } if err != nil { From a56558d0920b74b6553185de4aff79c3de534e01 Mon Sep 17 00:00:00 2001 From: maskpp Date: Tue, 29 Jul 2025 13:36:30 +0800 Subject: [PATCH 11/23] core/state: preallocate capacity for logs list (#32291) Improvement: preallocate capacity for `logs` at first to avoid reallocating multi times. --- core/state/statedb.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/state/statedb.go b/core/state/statedb.go index e8058850798..7aa6780cfa2 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -258,7 +258,7 @@ func (s *StateDB) GetLogs(hash common.Hash, blockNumber uint64, blockHash common } func (s *StateDB) Logs() []*types.Log { - var logs []*types.Log + logs := make([]*types.Log, 0, s.logSize) for _, lgs := range s.logs { logs = append(logs, lgs...) } From d14d4d2af07090a22f8651366146e3f17e09ed6b Mon Sep 17 00:00:00 2001 From: ericxtheodore Date: Wed, 30 Jul 2025 10:39:03 +0800 Subject: [PATCH 12/23] core/state: improve PrettyPrint function (#32293) --- core/state/access_list.go | 5 +---- core/state/transient_storage.go | 13 ++++--------- 2 files changed, 5 insertions(+), 13 deletions(-) diff --git a/core/state/access_list.go b/core/state/access_list.go index a58c2b20ea9..e3f17388648 100644 --- a/core/state/access_list.go +++ b/core/state/access_list.go @@ -145,10 +145,7 @@ func (al *accessList) Equal(other *accessList) bool { // PrettyPrint prints the contents of the access list in a human-readable form func (al *accessList) PrettyPrint() string { out := new(strings.Builder) - var sortedAddrs []common.Address - for addr := range al.addresses { - sortedAddrs = append(sortedAddrs, addr) - } + sortedAddrs := slices.Collect(maps.Keys(al.addresses)) slices.SortFunc(sortedAddrs, common.Address.Cmp) for _, addr := range sortedAddrs { idx := al.addresses[addr] diff --git a/core/state/transient_storage.go b/core/state/transient_storage.go index e63db39ebab..3bb49554255 100644 --- a/core/state/transient_storage.go +++ b/core/state/transient_storage.go @@ -18,6 +18,7 @@ package state import ( "fmt" + "maps" "slices" "strings" @@ -70,19 +71,13 @@ func (t transientStorage) Copy() transientStorage { // PrettyPrint prints the contents of the access list in a human-readable form func (t transientStorage) PrettyPrint() string { out := new(strings.Builder) - var sortedAddrs []common.Address - for addr := range t { - sortedAddrs = append(sortedAddrs, addr) - slices.SortFunc(sortedAddrs, common.Address.Cmp) - } + sortedAddrs := slices.Collect(maps.Keys(t)) + slices.SortFunc(sortedAddrs, common.Address.Cmp) for _, addr := range sortedAddrs { fmt.Fprintf(out, "%#x:", addr) - var sortedKeys []common.Hash storage := t[addr] - for key := range storage { - sortedKeys = append(sortedKeys, key) - } + sortedKeys := slices.Collect(maps.Keys(storage)) slices.SortFunc(sortedKeys, common.Hash.Cmp) for _, key := range sortedKeys { fmt.Fprintf(out, " %X : %X\n", key, storage[key]) From 2d95ba7d15879677cb451bbaa5d2c34c2b4f323d Mon Sep 17 00:00:00 2001 From: Daniel Katzan <108216499+dkatzan@users.noreply.github.com> Date: Thu, 31 Jul 2025 08:34:17 +0700 Subject: [PATCH 13/23] core/types: expose sigHash as Hash for SetCodeAuthorization (#32298) --- core/types/tx_setcode.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/core/types/tx_setcode.go b/core/types/tx_setcode.go index b8e38ef1f70..f2281d4ae76 100644 --- a/core/types/tx_setcode.go +++ b/core/types/tx_setcode.go @@ -89,7 +89,7 @@ type authorizationMarshaling struct { // SignSetCode creates a signed the SetCode authorization. func SignSetCode(prv *ecdsa.PrivateKey, auth SetCodeAuthorization) (SetCodeAuthorization, error) { - sighash := auth.sigHash() + sighash := auth.SigHash() sig, err := crypto.Sign(sighash[:], prv) if err != nil { return SetCodeAuthorization{}, err @@ -105,7 +105,8 @@ func SignSetCode(prv *ecdsa.PrivateKey, auth SetCodeAuthorization) (SetCodeAutho }, nil } -func (a *SetCodeAuthorization) sigHash() common.Hash { +// SigHash returns the hash of SetCodeAuthorization for signing. +func (a *SetCodeAuthorization) SigHash() common.Hash { return prefixedRlpHash(0x05, []any{ a.ChainID, a.Address, @@ -115,7 +116,7 @@ func (a *SetCodeAuthorization) sigHash() common.Hash { // Authority recovers the the authorizing account of an authorization. func (a *SetCodeAuthorization) Authority() (common.Address, error) { - sighash := a.sigHash() + sighash := a.SigHash() if !crypto.ValidateSignatureValues(a.V, a.R.ToBig(), a.S.ToBig(), true) { return common.Address{}, ErrInvalidSig } From 0814d991aba32ce4f2a0253f1c06c2da22788408 Mon Sep 17 00:00:00 2001 From: cui Date: Thu, 31 Jul 2025 09:48:31 +0800 Subject: [PATCH 14/23] common/hexutil: replace customized bit sizer with bit.Uintsize (#32304) --- common/hexutil/hexutil.go | 5 ++--- common/hexutil/json_test.go | 3 ++- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/common/hexutil/hexutil.go b/common/hexutil/hexutil.go index d3201850a8e..d6b6b867f2b 100644 --- a/common/hexutil/hexutil.go +++ b/common/hexutil/hexutil.go @@ -34,11 +34,10 @@ import ( "encoding/hex" "fmt" "math/big" + "math/bits" "strconv" ) -const uintBits = 32 << (uint64(^uint(0)) >> 63) - // Errors var ( ErrEmptyString = &decError{"empty hex string"} @@ -48,7 +47,7 @@ var ( ErrEmptyNumber = &decError{"hex string \"0x\""} ErrLeadingZero = &decError{"hex number with leading zero digits"} ErrUint64Range = &decError{"hex number > 64 bits"} - ErrUintRange = &decError{fmt.Sprintf("hex number > %d bits", uintBits)} + ErrUintRange = &decError{fmt.Sprintf("hex number > %d bits", bits.UintSize)} ErrBig256Range = &decError{"hex number > 256 bits"} ) diff --git a/common/hexutil/json_test.go b/common/hexutil/json_test.go index 7cca300951c..a0144384585 100644 --- a/common/hexutil/json_test.go +++ b/common/hexutil/json_test.go @@ -22,6 +22,7 @@ import ( "encoding/json" "errors" "math/big" + "math/bits" "testing" "github.com/holiman/uint256" @@ -384,7 +385,7 @@ func TestUnmarshalUint(t *testing.T) { for _, test := range unmarshalUintTests { var v Uint err := json.Unmarshal([]byte(test.input), &v) - if uintBits == 32 && test.wantErr32bit != nil { + if bits.UintSize == 32 && test.wantErr32bit != nil { checkError(t, test.input, err, test.wantErr32bit) continue } From 4d9d72806ccd09ce0d452abf77dc77aa3413b972 Mon Sep 17 00:00:00 2001 From: cui Date: Thu, 31 Jul 2025 09:53:31 +0800 Subject: [PATCH 15/23] accounts/abi: precompile regex (#32301) --- accounts/abi/abigen/bind.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/accounts/abi/abigen/bind.go b/accounts/abi/abigen/bind.go index 56e5e214de1..08624b04bab 100644 --- a/accounts/abi/abigen/bind.go +++ b/accounts/abi/abigen/bind.go @@ -33,6 +33,10 @@ import ( "github.com/ethereum/go-ethereum/log" ) +var ( + intRegex = regexp.MustCompile(`(u)?int([0-9]*)`) +) + func isKeyWord(arg string) bool { switch arg { case "break": @@ -299,7 +303,7 @@ func bindBasicType(kind abi.Type) string { case abi.AddressTy: return "common.Address" case abi.IntTy, abi.UintTy: - parts := regexp.MustCompile(`(u)?int([0-9]*)`).FindStringSubmatch(kind.String()) + parts := intRegex.FindStringSubmatch(kind.String()) switch parts[2] { case "8", "16", "32", "64": return fmt.Sprintf("%sint%s", parts[1], parts[2]) From d4a3bf1b23e3972fb82e085c0e29fe2c4647ed5c Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Thu, 31 Jul 2025 12:13:36 +0200 Subject: [PATCH 16/23] cmd/devp2p/internal/v4test: add test for ENRRequest (#32303) This adds a cross-client protocol test for a recently discovered bug in Nethermind. --- cmd/devp2p/internal/v4test/discv4tests.go | 32 +++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/cmd/devp2p/internal/v4test/discv4tests.go b/cmd/devp2p/internal/v4test/discv4tests.go index 963df6cdbc2..de97d7a2761 100644 --- a/cmd/devp2p/internal/v4test/discv4tests.go +++ b/cmd/devp2p/internal/v4test/discv4tests.go @@ -27,6 +27,7 @@ import ( "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/internal/utesting" "github.com/ethereum/go-ethereum/p2p/discover/v4wire" + "github.com/ethereum/go-ethereum/p2p/enode" ) const ( @@ -501,6 +502,36 @@ func FindnodeAmplificationWrongIP(t *utesting.T) { } } +func ENRRequest(t *utesting.T) { + t.Log(`This test sends an ENRRequest packet and expects a response containing a valid ENR.`) + + te := newTestEnv(Remote, Listen1, Listen2) + defer te.close() + bond(t, te) + + req := &v4wire.ENRRequest{Expiration: futureExpiration()} + hash := te.send(te.l1, req) + + response, _, err := te.read(te.l1) + if err != nil { + t.Fatal("read error:", err) + } + enrResp, ok := response.(*v4wire.ENRResponse) + if !ok { + t.Fatalf("expected ENRResponse packet, got %T", response) + } + if !bytes.Equal(enrResp.ReplyTok, hash) { + t.Errorf("wrong hash in response packet: got %x, want %x", enrResp.ReplyTok, hash) + } + node, err := enode.New(enode.ValidSchemes, &enrResp.Record) + if err != nil { + t.Errorf("invalid record in response: %v", err) + } + if node.ID() != te.remote.ID() { + t.Errorf("wrong node ID in response: got %v, want %v", node.ID(), te.remote.ID()) + } +} + var AllTests = []utesting.Test{ {Name: "Ping/Basic", Fn: BasicPing}, {Name: "Ping/WrongTo", Fn: PingWrongTo}, @@ -510,6 +541,7 @@ var AllTests = []utesting.Test{ {Name: "Ping/PastExpiration", Fn: PingPastExpiration}, {Name: "Ping/WrongPacketType", Fn: WrongPacketType}, {Name: "Ping/BondThenPingWithWrongFrom", Fn: BondThenPingWithWrongFrom}, + {Name: "ENRRequest", Fn: ENRRequest}, {Name: "Findnode/WithoutEndpointProof", Fn: FindnodeWithoutEndpointProof}, {Name: "Findnode/BasicFindnode", Fn: BasicFindnode}, {Name: "Findnode/UnsolicitedNeighbors", Fn: UnsolicitedNeighbors}, From 23da91f73b83eb1d101889b2f773c6f0a80a8f15 Mon Sep 17 00:00:00 2001 From: rjl493456442 Date: Fri, 1 Aug 2025 10:23:23 +0800 Subject: [PATCH 17/23] trie: reduce the memory allocation in trie hashing (#31902) This pull request optimizes trie hashing by reducing memory allocation overhead. Specifically: - define a fullNodeEncoder pool to reuse encoders and avoid memory allocations. - simplify the encoding logic for shortNode and fullNode by getting rid of the Go interfaces. --- trie/hasher.go | 179 ++++++++++++++++++++++++---------------------- trie/iterator.go | 6 +- trie/node.go | 4 -- trie/node_enc.go | 23 ++++-- trie/proof.go | 14 ++-- trie/trie.go | 6 +- trie/trie_test.go | 1 - 7 files changed, 121 insertions(+), 112 deletions(-) diff --git a/trie/hasher.go b/trie/hasher.go index 16606808c9f..a2a1f5b662c 100644 --- a/trie/hasher.go +++ b/trie/hasher.go @@ -17,6 +17,8 @@ package trie import ( + "bytes" + "fmt" "sync" "github.com/ethereum/go-ethereum/crypto" @@ -54,7 +56,7 @@ func returnHasherToPool(h *hasher) { } // hash collapses a node down into a hash node. -func (h *hasher) hash(n node, force bool) node { +func (h *hasher) hash(n node, force bool) []byte { // Return the cached hash if it's available if hash, _ := n.cache(); hash != nil { return hash @@ -62,101 +64,110 @@ func (h *hasher) hash(n node, force bool) node { // Trie not processed yet, walk the children switch n := n.(type) { case *shortNode: - collapsed := h.hashShortNodeChildren(n) - hashed := h.shortnodeToHash(collapsed, force) - if hn, ok := hashed.(hashNode); ok { - n.flags.hash = hn - } else { - n.flags.hash = nil + enc := h.encodeShortNode(n) + if len(enc) < 32 && !force { + // Nodes smaller than 32 bytes are embedded directly in their parent. + // In such cases, return the raw encoded blob instead of the node hash. + // It's essential to deep-copy the node blob, as the underlying buffer + // of enc will be reused later. + buf := make([]byte, len(enc)) + copy(buf, enc) + return buf } - return hashed + hash := h.hashData(enc) + n.flags.hash = hash + return hash + case *fullNode: - collapsed := h.hashFullNodeChildren(n) - hashed := h.fullnodeToHash(collapsed, force) - if hn, ok := hashed.(hashNode); ok { - n.flags.hash = hn - } else { - n.flags.hash = nil + enc := h.encodeFullNode(n) + if len(enc) < 32 && !force { + // Nodes smaller than 32 bytes are embedded directly in their parent. + // In such cases, return the raw encoded blob instead of the node hash. + // It's essential to deep-copy the node blob, as the underlying buffer + // of enc will be reused later. + buf := make([]byte, len(enc)) + copy(buf, enc) + return buf } - return hashed - default: - // Value and hash nodes don't have children, so they're left as were + hash := h.hashData(enc) + n.flags.hash = hash + return hash + + case hashNode: + // hash nodes don't have children, so they're left as were return n + + default: + panic(fmt.Errorf("unexpected node type, %T", n)) } } -// hashShortNodeChildren returns a copy of the supplied shortNode, with its child -// being replaced by either the hash or an embedded node if the child is small. -func (h *hasher) hashShortNodeChildren(n *shortNode) *shortNode { - var collapsed shortNode - collapsed.Key = hexToCompact(n.Key) - switch n.Val.(type) { - case *fullNode, *shortNode: - collapsed.Val = h.hash(n.Val, false) - default: - collapsed.Val = n.Val +// encodeShortNode encodes the provided shortNode into the bytes. Notably, the +// return slice must be deep-copied explicitly, otherwise the underlying slice +// will be reused later. +func (h *hasher) encodeShortNode(n *shortNode) []byte { + // Encode leaf node + if hasTerm(n.Key) { + var ln leafNodeEncoder + ln.Key = hexToCompact(n.Key) + ln.Val = n.Val.(valueNode) + ln.encode(h.encbuf) + return h.encodedBytes() } - return &collapsed + // Encode extension node + var en extNodeEncoder + en.Key = hexToCompact(n.Key) + en.Val = h.hash(n.Val, false) + en.encode(h.encbuf) + return h.encodedBytes() +} + +// fnEncoderPool is the pool for storing shared fullNode encoder to mitigate +// the significant memory allocation overhead. +var fnEncoderPool = sync.Pool{ + New: func() interface{} { + var enc fullnodeEncoder + return &enc + }, } -// hashFullNodeChildren returns a copy of the supplied fullNode, with its child -// being replaced by either the hash or an embedded node if the child is small. -func (h *hasher) hashFullNodeChildren(n *fullNode) *fullNode { - var children [17]node +// encodeFullNode encodes the provided fullNode into the bytes. Notably, the +// return slice must be deep-copied explicitly, otherwise the underlying slice +// will be reused later. +func (h *hasher) encodeFullNode(n *fullNode) []byte { + fn := fnEncoderPool.Get().(*fullnodeEncoder) + fn.reset() + if h.parallel { var wg sync.WaitGroup for i := 0; i < 16; i++ { - if child := n.Children[i]; child != nil { - wg.Add(1) - go func(i int) { - hasher := newHasher(false) - children[i] = hasher.hash(child, false) - returnHasherToPool(hasher) - wg.Done() - }(i) - } else { - children[i] = nilValueNode + if n.Children[i] == nil { + continue } + wg.Add(1) + go func(i int) { + defer wg.Done() + + h := newHasher(false) + fn.Children[i] = h.hash(n.Children[i], false) + returnHasherToPool(h) + }(i) } wg.Wait() } else { for i := 0; i < 16; i++ { if child := n.Children[i]; child != nil { - children[i] = h.hash(child, false) - } else { - children[i] = nilValueNode + fn.Children[i] = h.hash(child, false) } } } if n.Children[16] != nil { - children[16] = n.Children[16] - } - return &fullNode{flags: nodeFlag{}, Children: children} -} - -// shortNodeToHash computes the hash of the given shortNode. The shortNode must -// first be collapsed, with its key converted to compact form. If the RLP-encoded -// node data is smaller than 32 bytes, the node itself is returned. -func (h *hasher) shortnodeToHash(n *shortNode, force bool) node { - n.encode(h.encbuf) - enc := h.encodedBytes() - - if len(enc) < 32 && !force { - return n // Nodes smaller than 32 bytes are stored inside their parent + fn.Children[16] = n.Children[16].(valueNode) } - return h.hashData(enc) -} - -// fullnodeToHash computes the hash of the given fullNode. If the RLP-encoded -// node data is smaller than 32 bytes, the node itself is returned. -func (h *hasher) fullnodeToHash(n *fullNode, force bool) node { - n.encode(h.encbuf) - enc := h.encodedBytes() + fn.encode(h.encbuf) + fnEncoderPool.Put(fn) - if len(enc) < 32 && !force { - return n // Nodes smaller than 32 bytes are stored inside their parent - } - return h.hashData(enc) + return h.encodedBytes() } // encodedBytes returns the result of the last encoding operation on h.encbuf. @@ -175,9 +186,10 @@ func (h *hasher) encodedBytes() []byte { return h.tmp } -// hashData hashes the provided data -func (h *hasher) hashData(data []byte) hashNode { - n := make(hashNode, 32) +// hashData hashes the provided data. It is safe to modify the returned slice after +// the function returns. +func (h *hasher) hashData(data []byte) []byte { + n := make([]byte, 32) h.sha.Reset() h.sha.Write(data) h.sha.Read(n) @@ -192,20 +204,17 @@ func (h *hasher) hashDataTo(dst, data []byte) { h.sha.Read(dst) } -// proofHash is used to construct trie proofs, and returns the 'collapsed' -// node (for later RLP encoding) as well as the hashed node -- unless the -// node is smaller than 32 bytes, in which case it will be returned as is. -// This method does not do anything on value- or hash-nodes. -func (h *hasher) proofHash(original node) (collapsed, hashed node) { +// proofHash is used to construct trie proofs, returning the rlp-encoded node blobs. +// Note, only resolved node (shortNode or fullNode) is expected for proofing. +// +// It is safe to modify the returned slice after the function returns. +func (h *hasher) proofHash(original node) []byte { switch n := original.(type) { case *shortNode: - sn := h.hashShortNodeChildren(n) - return sn, h.shortnodeToHash(sn, false) + return bytes.Clone(h.encodeShortNode(n)) case *fullNode: - fn := h.hashFullNodeChildren(n) - return fn, h.fullnodeToHash(fn, false) + return bytes.Clone(h.encodeFullNode(n)) default: - // Value and hash nodes don't have children, so they're left as were - return n, n + panic(fmt.Errorf("unexpected node type, %T", original)) } } diff --git a/trie/iterator.go b/trie/iterator.go index fa016110636..e6fedf24309 100644 --- a/trie/iterator.go +++ b/trie/iterator.go @@ -240,9 +240,9 @@ func (it *nodeIterator) LeafProof() [][]byte { for i, item := range it.stack[:len(it.stack)-1] { // Gather nodes that end up as hash nodes (or the root) - node, hashed := hasher.proofHash(item.node) - if _, ok := hashed.(hashNode); ok || i == 0 { - proofs = append(proofs, nodeToBytes(node)) + enc := hasher.proofHash(item.node) + if len(enc) >= 32 || i == 0 { + proofs = append(proofs, enc) } } return proofs diff --git a/trie/node.go b/trie/node.go index 96f077ebbb7..74fac4fd4ea 100644 --- a/trie/node.go +++ b/trie/node.go @@ -68,10 +68,6 @@ type ( } ) -// nilValueNode is used when collapsing internal trie nodes for hashing, since -// unset children need to serialize correctly. -var nilValueNode = valueNode(nil) - // EncodeRLP encodes a full node into the consensus RLP format. func (n *fullNode) EncodeRLP(w io.Writer) error { eb := rlp.NewEncoderBuffer(w) diff --git a/trie/node_enc.go b/trie/node_enc.go index c95587eeabb..02b93ee6f3e 100644 --- a/trie/node_enc.go +++ b/trie/node_enc.go @@ -42,18 +42,29 @@ func (n *fullNode) encode(w rlp.EncoderBuffer) { func (n *fullnodeEncoder) encode(w rlp.EncoderBuffer) { offset := w.List() - for _, c := range n.Children { - if c == nil { + for i, c := range n.Children { + if len(c) == 0 { w.Write(rlp.EmptyString) - } else if len(c) < 32 { - w.Write(c) // rawNode } else { - w.WriteBytes(c) // hashNode + // valueNode or hashNode + if i == 16 || len(c) >= 32 { + w.WriteBytes(c) + } else { + w.Write(c) // rawNode + } } } w.ListEnd(offset) } +func (n *fullnodeEncoder) reset() { + for i, c := range n.Children { + if len(c) != 0 { + n.Children[i] = n.Children[i][:0] + } + } +} + func (n *shortNode) encode(w rlp.EncoderBuffer) { offset := w.List() w.WriteBytes(n.Key) @@ -70,7 +81,7 @@ func (n *extNodeEncoder) encode(w rlp.EncoderBuffer) { w.WriteBytes(n.Key) if n.Val == nil { - w.Write(rlp.EmptyString) + w.Write(rlp.EmptyString) // theoretically impossible to happen } else if len(n.Val) < 32 { w.Write(n.Val) // rawNode } else { diff --git a/trie/proof.go b/trie/proof.go index 751d6f620f3..53b7acc30c9 100644 --- a/trie/proof.go +++ b/trie/proof.go @@ -22,6 +22,7 @@ import ( "fmt" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/log" ) @@ -85,16 +86,9 @@ func (t *Trie) Prove(key []byte, proofDb ethdb.KeyValueWriter) error { defer returnHasherToPool(hasher) for i, n := range nodes { - var hn node - n, hn = hasher.proofHash(n) - if hash, ok := hn.(hashNode); ok || i == 0 { - // If the node's database encoding is a hash (or is the - // root node), it becomes a proof element. - enc := nodeToBytes(n) - if !ok { - hash = hasher.hashData(enc) - } - proofDb.Put(hash, enc) + enc := hasher.proofHash(n) + if len(enc) >= 32 || i == 0 { + proofDb.Put(crypto.Keccak256(enc), enc) } } return nil diff --git a/trie/trie.go b/trie/trie.go index fdb4da9be47..222bf8b1f02 100644 --- a/trie/trie.go +++ b/trie/trie.go @@ -626,7 +626,7 @@ func (t *Trie) resolveAndTrack(n hashNode, prefix []byte) (node, error) { // Hash returns the root hash of the trie. It does not write to the // database and can be used even if the trie doesn't have one. func (t *Trie) Hash() common.Hash { - return common.BytesToHash(t.hashRoot().(hashNode)) + return common.BytesToHash(t.hashRoot()) } // Commit collects all dirty nodes in the trie and replaces them with the @@ -677,9 +677,9 @@ func (t *Trie) Commit(collectLeaf bool) (common.Hash, *trienode.NodeSet) { } // hashRoot calculates the root hash of the given trie -func (t *Trie) hashRoot() node { +func (t *Trie) hashRoot() []byte { if t.root == nil { - return hashNode(types.EmptyRootHash.Bytes()) + return types.EmptyRootHash.Bytes() } // If the number of changes is below 100, we let one thread handle it h := newHasher(t.unhashed >= 100) diff --git a/trie/trie_test.go b/trie/trie_test.go index b806ae6b0ce..edd85677fe6 100644 --- a/trie/trie_test.go +++ b/trie/trie_test.go @@ -863,7 +863,6 @@ func (s *spongeDb) Flush() { s.sponge.Write([]byte(key)) s.sponge.Write([]byte(s.values[key])) } - fmt.Println(len(s.keys)) } // spongeBatch is a dummy batch which immediately writes to the underlying spongedb From 17d65e9451111288d96f8fb6befb8544a94b6d50 Mon Sep 17 00:00:00 2001 From: lmittmann <3458786+lmittmann@users.noreply.github.com> Date: Fri, 1 Aug 2025 13:57:38 +0200 Subject: [PATCH 18/23] core/vm: add configurable jumpdest analysis cache (#32143) This adds a method on vm.EVM to set the jumpdest cache implementation. It can be used to maintain an analysis cache across VM invocations, to improve performance by skipping the analysis for already known contracts. --------- Co-authored-by: lmittmann Co-authored-by: Felix Lange --- core/vm/analysis_legacy.go | 20 +++++++------- core/vm/analysis_legacy_test.go | 2 +- core/vm/contract.go | 16 +++++------ core/vm/evm.go | 12 ++++++--- core/vm/jumpdests.go | 47 +++++++++++++++++++++++++++++++++ 5 files changed, 74 insertions(+), 23 deletions(-) create mode 100644 core/vm/jumpdests.go diff --git a/core/vm/analysis_legacy.go b/core/vm/analysis_legacy.go index 38af9084aca..a445e2048e3 100644 --- a/core/vm/analysis_legacy.go +++ b/core/vm/analysis_legacy.go @@ -25,16 +25,16 @@ const ( set7BitsMask = uint16(0b111_1111) ) -// bitvec is a bit vector which maps bytes in a program. +// BitVec is a bit vector which maps bytes in a program. // An unset bit means the byte is an opcode, a set bit means // it's data (i.e. argument of PUSHxx). -type bitvec []byte +type BitVec []byte -func (bits bitvec) set1(pos uint64) { +func (bits BitVec) set1(pos uint64) { bits[pos/8] |= 1 << (pos % 8) } -func (bits bitvec) setN(flag uint16, pos uint64) { +func (bits BitVec) setN(flag uint16, pos uint64) { a := flag << (pos % 8) bits[pos/8] |= byte(a) if b := byte(a >> 8); b != 0 { @@ -42,13 +42,13 @@ func (bits bitvec) setN(flag uint16, pos uint64) { } } -func (bits bitvec) set8(pos uint64) { +func (bits BitVec) set8(pos uint64) { a := byte(0xFF << (pos % 8)) bits[pos/8] |= a bits[pos/8+1] = ^a } -func (bits bitvec) set16(pos uint64) { +func (bits BitVec) set16(pos uint64) { a := byte(0xFF << (pos % 8)) bits[pos/8] |= a bits[pos/8+1] = 0xFF @@ -56,23 +56,23 @@ func (bits bitvec) set16(pos uint64) { } // codeSegment checks if the position is in a code segment. -func (bits *bitvec) codeSegment(pos uint64) bool { +func (bits *BitVec) codeSegment(pos uint64) bool { return (((*bits)[pos/8] >> (pos % 8)) & 1) == 0 } // codeBitmap collects data locations in code. -func codeBitmap(code []byte) bitvec { +func codeBitmap(code []byte) BitVec { // The bitmap is 4 bytes longer than necessary, in case the code // ends with a PUSH32, the algorithm will set bits on the // bitvector outside the bounds of the actual code. - bits := make(bitvec, len(code)/8+1+4) + bits := make(BitVec, len(code)/8+1+4) return codeBitmapInternal(code, bits) } // codeBitmapInternal is the internal implementation of codeBitmap. // It exists for the purpose of being able to run benchmark tests // without dynamic allocations affecting the results. -func codeBitmapInternal(code, bits bitvec) bitvec { +func codeBitmapInternal(code, bits BitVec) BitVec { for pc := uint64(0); pc < uint64(len(code)); { op := OpCode(code[pc]) pc++ diff --git a/core/vm/analysis_legacy_test.go b/core/vm/analysis_legacy_test.go index 471d2b4ffba..f84a4abc926 100644 --- a/core/vm/analysis_legacy_test.go +++ b/core/vm/analysis_legacy_test.go @@ -90,7 +90,7 @@ func BenchmarkJumpdestOpAnalysis(bench *testing.B) { for i := range code { code[i] = byte(op) } - bits := make(bitvec, len(code)/8+1+4) + bits := make(BitVec, len(code)/8+1+4) b.ResetTimer() for i := 0; i < b.N; i++ { clear(bits) diff --git a/core/vm/contract.go b/core/vm/contract.go index 0eaa91d9596..165ca833f88 100644 --- a/core/vm/contract.go +++ b/core/vm/contract.go @@ -31,8 +31,8 @@ type Contract struct { caller common.Address address common.Address - jumpdests map[common.Hash]bitvec // Aggregated result of JUMPDEST analysis. - analysis bitvec // Locally cached result of JUMPDEST analysis + jumpDests JumpDestCache // Aggregated result of JUMPDEST analysis. + analysis BitVec // Locally cached result of JUMPDEST analysis Code []byte CodeHash common.Hash @@ -47,15 +47,15 @@ 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 map[common.Hash]bitvec) *Contract { - // Initialize the jump analysis map if it's nil, mostly for tests +func NewContract(caller common.Address, address common.Address, value *uint256.Int, gas uint64, jumpDests JumpDestCache) *Contract { + // Initialize the jump analysis cache if it's nil, mostly for tests if jumpDests == nil { - jumpDests = make(map[common.Hash]bitvec) + jumpDests = newMapJumpDests() } return &Contract{ caller: caller, address: address, - jumpdests: jumpDests, + jumpDests: jumpDests, Gas: gas, value: value, } @@ -87,12 +87,12 @@ func (c *Contract) isCode(udest uint64) bool { // contracts ( not temporary initcode), we store the analysis in a map if c.CodeHash != (common.Hash{}) { // Does parent context have the analysis? - analysis, exist := c.jumpdests[c.CodeHash] + analysis, exist := c.jumpDests.Load(c.CodeHash) if !exist { // Do the analysis and save in parent context // We do not need to store it in c.analysis analysis = codeBitmap(c.Code) - c.jumpdests[c.CodeHash] = analysis + c.jumpDests.Store(c.CodeHash, analysis) } // Also stash it in current contract for faster access c.analysis = analysis diff --git a/core/vm/evm.go b/core/vm/evm.go index b45a4345453..143b7e08a22 100644 --- a/core/vm/evm.go +++ b/core/vm/evm.go @@ -122,9 +122,8 @@ type EVM struct { // precompiles holds the precompiled contracts for the current epoch precompiles map[common.Address]PrecompiledContract - // jumpDests is the aggregated result of JUMPDEST analysis made through - // the life cycle of EVM. - jumpDests map[common.Hash]bitvec + // jumpDests stores results of JUMPDEST analysis. + jumpDests JumpDestCache } // NewEVM constructs an EVM instance with the supplied block context, state @@ -138,7 +137,7 @@ func NewEVM(blockCtx BlockContext, statedb StateDB, chainConfig *params.ChainCon Config: config, chainConfig: chainConfig, chainRules: chainConfig.Rules(blockCtx.BlockNumber, blockCtx.Random != nil, blockCtx.Time), - jumpDests: make(map[common.Hash]bitvec), + jumpDests: newMapJumpDests(), } evm.precompiles = activePrecompiledContracts(evm.chainRules) evm.interpreter = NewEVMInterpreter(evm) @@ -152,6 +151,11 @@ func (evm *EVM) SetPrecompiles(precompiles PrecompiledContracts) { evm.precompiles = precompiles } +// SetJumpDestCache configures the analysis cache. +func (evm *EVM) SetJumpDestCache(jumpDests JumpDestCache) { + evm.jumpDests = jumpDests +} + // SetTxContext resets the EVM with a new transaction context. // This is not threadsafe and should only be done very cautiously. func (evm *EVM) SetTxContext(txCtx TxContext) { diff --git a/core/vm/jumpdests.go b/core/vm/jumpdests.go new file mode 100644 index 00000000000..1a30c1943f2 --- /dev/null +++ b/core/vm/jumpdests.go @@ -0,0 +1,47 @@ +// Copyright 2024 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 vm + +import "github.com/ethereum/go-ethereum/common" + +// JumpDestCache represents the cache of jumpdest analysis results. +type JumpDestCache interface { + // Load retrieves the cached jumpdest analysis for the given code hash. + // Returns the BitVec and true if found, or nil and false if not cached. + Load(codeHash common.Hash) (BitVec, bool) + + // Store saves the jumpdest analysis for the given code hash. + Store(codeHash common.Hash, vec BitVec) +} + +// mapJumpDests is the default implementation of JumpDests using a map. +// This implementation is not thread-safe and is meant to be used per EVM instance. +type mapJumpDests map[common.Hash]BitVec + +// newMapJumpDests creates a new map-based JumpDests implementation. +func newMapJumpDests() JumpDestCache { + return make(mapJumpDests) +} + +func (j mapJumpDests) Load(codeHash common.Hash) (BitVec, bool) { + vec, ok := j[codeHash] + return vec, ok +} + +func (j mapJumpDests) Store(codeHash common.Hash, vec BitVec) { + j[codeHash] = vec +} From 9c58810e717e7c48dff31ebc7280ba47675597a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Andr=C3=B3il?= Date: Fri, 1 Aug 2025 14:00:00 +0200 Subject: [PATCH 19/23] eth: fix typos and outdated comments (#32324) --- eth/ethconfig/config.go | 2 +- eth/filters/filter.go | 1 - eth/filters/filter_system.go | 2 +- 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/eth/ethconfig/config.go b/eth/ethconfig/config.go index 7eba6a6408b..82c3c500a77 100644 --- a/eth/ethconfig/config.go +++ b/eth/ethconfig/config.go @@ -154,7 +154,7 @@ type Config struct { // RPCEVMTimeout is the global timeout for eth-call. RPCEVMTimeout time.Duration - // RPCTxFeeCap is the global transaction fee(price * gaslimit) cap for + // RPCTxFeeCap is the global transaction fee (price * gas limit) cap for // send-transaction variants. The unit is ether. RPCTxFeeCap float64 diff --git a/eth/filters/filter.go b/eth/filters/filter.go index ada478ae1d1..c4d787eb229 100644 --- a/eth/filters/filter.go +++ b/eth/filters/filter.go @@ -456,7 +456,6 @@ func (f *Filter) blockLogs(ctx context.Context, header *types.Header) ([]*types. // checkMatches checks if the receipts belonging to the given header contain any log events that // match the filter criteria. This function is called when the bloom filter signals a potential match. -// skipFilter signals all logs of the given block are requested. func (f *Filter) checkMatches(ctx context.Context, header *types.Header) ([]*types.Log, error) { hash := header.Hash() // Logs in cache are partially filled with context data diff --git a/eth/filters/filter_system.go b/eth/filters/filter_system.go index 82c91266c05..751cd417e8f 100644 --- a/eth/filters/filter_system.go +++ b/eth/filters/filter_system.go @@ -207,7 +207,7 @@ type EventSystem struct { } // NewEventSystem creates a new manager that listens for event on the given mux, -// parses and filters them. It uses the all map to retrieve filter changes. The +// parses and filters them. It uses an internal map to retrieve filter changes. The // work loop holds its own index that is used to forward events to filters. // // The returned manager has a loop that needs to be stopped with the Stop function From 4d1264fd0863dbf5796cefbb4a8d92bb504b166a Mon Sep 17 00:00:00 2001 From: Minhyuk Kim Date: Thu, 24 Jul 2025 14:22:19 +0900 Subject: [PATCH 20/23] Allow ErrGasLimitTooHigh errors when executing transactions for gas limit estimation to continue estimating with lower caps --- eth/ethconfig/config.go | 2 +- eth/gasestimator/gasestimator.go | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/eth/ethconfig/config.go b/eth/ethconfig/config.go index 82c3c500a77..c73553acb73 100644 --- a/eth/ethconfig/config.go +++ b/eth/ethconfig/config.go @@ -65,7 +65,7 @@ var Defaults = Config{ Miner: miner.DefaultConfig, TxPool: legacypool.DefaultConfig, BlobPool: blobpool.DefaultConfig, - RPCGasCap: 50000000, + RPCGasCap: 30000000, RPCEVMTimeout: 5 * time.Second, GPO: FullNodeGPO, RPCTxFeeCap: 1, // 1 ether diff --git a/eth/gasestimator/gasestimator.go b/eth/gasestimator/gasestimator.go index 7e9d8125de8..724e8cbe898 100644 --- a/eth/gasestimator/gasestimator.go +++ b/eth/gasestimator/gasestimator.go @@ -62,6 +62,9 @@ func Estimate(ctx context.Context, call *core.Message, opts *Options, gasCap uin if call.GasLimit >= params.TxGas { hi = call.GasLimit } + if hi >= params.MaxTxGas { + hi = params.MaxTxGas + } // Normalize the max fee per gas the call is willing to spend. var feeCap *big.Int if call.GasFeeCap != nil { @@ -209,6 +212,9 @@ func execute(ctx context.Context, call *core.Message, opts *Options, gasLimit ui if errors.Is(err, core.ErrIntrinsicGas) { return true, nil, nil // Special case, raise gas limit } + if errors.Is(err, core.ErrGasLimitTooHigh) { + return true, nil, nil // Special case, lower gas limit + } return true, nil, err // Bail out } return result.Failed(), result, nil From db12f63bf6fda2b161f92253910d2d3d9a9950f2 Mon Sep 17 00:00:00 2001 From: Minhyuk Kim Date: Mon, 28 Jul 2025 17:01:45 +0900 Subject: [PATCH 21/23] Use params.MaxTxGas for RPCGasCap --- eth/ethconfig/config.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/eth/ethconfig/config.go b/eth/ethconfig/config.go index c73553acb73..b19b3a4502b 100644 --- a/eth/ethconfig/config.go +++ b/eth/ethconfig/config.go @@ -65,7 +65,7 @@ var Defaults = Config{ Miner: miner.DefaultConfig, TxPool: legacypool.DefaultConfig, BlobPool: blobpool.DefaultConfig, - RPCGasCap: 30000000, + RPCGasCap: params.MaxTxGas, RPCEVMTimeout: 5 * time.Second, GPO: FullNodeGPO, RPCTxFeeCap: 1, // 1 ether From a7857cdf978150ffbb9ff6572ae958a6f5845856 Mon Sep 17 00:00:00 2001 From: Minhyuk Kim Date: Thu, 31 Jul 2025 11:45:03 +0900 Subject: [PATCH 22/23] Cap maximum gas allowance to EIP-7825 in gas estimates only for osaka --- eth/gasestimator/gasestimator.go | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/eth/gasestimator/gasestimator.go b/eth/gasestimator/gasestimator.go index 724e8cbe898..6e79fbd62b0 100644 --- a/eth/gasestimator/gasestimator.go +++ b/eth/gasestimator/gasestimator.go @@ -62,9 +62,23 @@ func Estimate(ctx context.Context, call *core.Message, opts *Options, gasCap uin if call.GasLimit >= params.TxGas { hi = call.GasLimit } - if hi >= params.MaxTxGas { - hi = params.MaxTxGas + + // Cap the maximum gas allowance according to EIP-7825 if the estimation targets Osaka + if hi > params.MaxTxGas { + blockNumber, blockTime := opts.Header.Number, opts.Header.Time + if opts.BlockOverrides != nil { + if opts.BlockOverrides.Number != nil { + blockNumber = opts.BlockOverrides.Number.ToInt() + } + if opts.BlockOverrides.Time != nil { + blockTime = uint64(*opts.BlockOverrides.Time) + } + } + if opts.Config.IsOsaka(blockNumber, blockTime) { + hi = params.MaxTxGas + } } + // Normalize the max fee per gas the call is willing to spend. var feeCap *big.Int if call.GasFeeCap != nil { From 5ef01bbb39365a5bcf41c1c081b062431d20a6f8 Mon Sep 17 00:00:00 2001 From: Gary Rong Date: Fri, 1 Aug 2025 20:29:23 +0800 Subject: [PATCH 23/23] core/vm, internal/ethapi: fix lint --- eth/ethconfig/config.go | 2 +- internal/ethapi/override/override_test.go | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/eth/ethconfig/config.go b/eth/ethconfig/config.go index b19b3a4502b..82c3c500a77 100644 --- a/eth/ethconfig/config.go +++ b/eth/ethconfig/config.go @@ -65,7 +65,7 @@ var Defaults = Config{ Miner: miner.DefaultConfig, TxPool: legacypool.DefaultConfig, BlobPool: blobpool.DefaultConfig, - RPCGasCap: params.MaxTxGas, + RPCGasCap: 50000000, RPCEVMTimeout: 5 * time.Second, GPO: FullNodeGPO, RPCTxFeeCap: 1, // 1 ether diff --git a/internal/ethapi/override/override_test.go b/internal/ethapi/override/override_test.go index 02a17c13317..41b4f2c2533 100644 --- a/internal/ethapi/override/override_test.go +++ b/internal/ethapi/override/override_test.go @@ -31,6 +31,10 @@ import ( type precompileContract struct{} +func (p *precompileContract) Name() string { + panic("implement me") +} + func (p *precompileContract) RequiredGas(input []byte) uint64 { return 0 } func (p *precompileContract) Run(input []byte) ([]byte, error) { return nil, nil }