From b1eb33ce8b18a39985a5e670dac76a1fad5b4d02 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Fri, 26 Sep 2025 11:41:02 +0200 Subject: [PATCH 01/61] version: begin v1.16.5 release cycle --- version/version.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/version/version.go b/version/version.go index 6430140631..db4e5394b9 100644 --- a/version/version.go +++ b/version/version.go @@ -17,8 +17,8 @@ package version const ( - Major = 1 // Major version component of the current release - Minor = 16 // Minor version component of the current release - Patch = 4 // Patch version component of the current release - Meta = "stable" // Version metadata to append to the version string + Major = 1 // Major version component of the current release + Minor = 16 // Minor version component of the current release + Patch = 5 // Patch version component of the current release + Meta = "unstable" // Version metadata to append to the version string ) From a8f7965d5809dd6f19cf524c0e82f24d6aedc906 Mon Sep 17 00:00:00 2001 From: wit liu <765765346@qq.com> Date: Fri, 26 Sep 2025 20:11:50 +0800 Subject: [PATCH 02/61] internal/ethapi: fix outdated comments (#32751) Fix outdated comments --- internal/ethapi/api.go | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index ebb8ece730..c60aad5617 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -460,10 +460,10 @@ func decodeHash(s string) (h common.Hash, inputLength int, err error) { } // GetHeaderByNumber returns the requested canonical block header. -// - When blockNr is -1 the chain pending header is returned. -// - When blockNr is -2 the chain latest header is returned. -// - When blockNr is -3 the chain finalized header is returned. -// - When blockNr is -4 the chain safe header is returned. +// - When number is -1 the chain pending header is returned. +// - When number is -2 the chain latest header is returned. +// - When number is -3 the chain finalized header is returned. +// - When number is -4 the chain safe header is returned. func (api *BlockChainAPI) GetHeaderByNumber(ctx context.Context, number rpc.BlockNumber) (map[string]interface{}, error) { header, err := api.b.HeaderByNumber(ctx, number) if header != nil && err == nil { @@ -489,10 +489,10 @@ func (api *BlockChainAPI) GetHeaderByHash(ctx context.Context, hash common.Hash) } // GetBlockByNumber returns the requested canonical block. -// - When blockNr is -1 the chain pending block is returned. -// - When blockNr is -2 the chain latest block is returned. -// - When blockNr is -3 the chain finalized block is returned. -// - When blockNr is -4 the chain safe block is returned. +// - When number is -1 the chain pending block is returned. +// - When number is -2 the chain latest block is returned. +// - When number is -3 the chain finalized block is returned. +// - When number is -4 the chain safe block is returned. // - When fullTx is true all transactions in the block are returned, otherwise // only the transaction hash is returned. func (api *BlockChainAPI) GetBlockByNumber(ctx context.Context, number rpc.BlockNumber, fullTx bool) (map[string]interface{}, error) { From 2e2fece0bb439801a36177b263705a65c98c381b Mon Sep 17 00:00:00 2001 From: Matus Kysel Date: Fri, 26 Sep 2025 15:12:28 +0200 Subject: [PATCH 03/61] ethapi: reject oversize storage keys before hex decode (#32750) Bail out of decodeHash when the raw hex string is longer than 32 byte before actually decoding. --------- Co-authored-by: lightclient --- internal/ethapi/api.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index c60aad5617..2432bb70b8 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -449,13 +449,13 @@ func decodeHash(s string) (h common.Hash, inputLength int, err error) { if (len(s) & 1) > 0 { s = "0" + s } + if len(s) > 64 { + return common.Hash{}, len(s) / 2, errors.New("hex string too long, want at most 32 bytes") + } b, err := hex.DecodeString(s) if err != nil { return common.Hash{}, 0, errors.New("hex string invalid") } - if len(b) > 32 { - return common.Hash{}, len(b), errors.New("hex string too long, want at most 32 bytes") - } return common.BytesToHash(b), len(b), nil } From 16b735fddd840ad85f6cfdcdc59b377d9b29088c Mon Sep 17 00:00:00 2001 From: VolodymyrBg Date: Fri, 26 Sep 2025 16:26:22 +0300 Subject: [PATCH 04/61] signer/core: fix TestSignTx to decode res2 (#32749) Decode the modified transaction and verify the value differs from original. --------- Co-authored-by: lightclient --- signer/core/api_test.go | 42 ++++++++++++++++++++--------------------- 1 file changed, 20 insertions(+), 22 deletions(-) diff --git a/signer/core/api_test.go b/signer/core/api_test.go index 69229dadaf..0e16a1b7fd 100644 --- a/signer/core/api_test.go +++ b/signer/core/api_test.go @@ -18,7 +18,6 @@ package core_test import ( "bytes" - "context" "fmt" "math/big" "os" @@ -97,12 +96,12 @@ func (ui *headlessUi) ApproveNewAccount(request *core.NewAccountRequest) (core.N } func (ui *headlessUi) ShowError(message string) { - //stdout is used by communication + // stdout is used by communication fmt.Fprintln(os.Stderr, message) } func (ui *headlessUi) ShowInfo(message string) { - //stdout is used by communication + // stdout is used by communication fmt.Fprintln(os.Stderr, message) } @@ -128,7 +127,7 @@ func setup(t *testing.T) (*core.SignerAPI, *headlessUi) { func createAccount(ui *headlessUi, api *core.SignerAPI, t *testing.T) { ui.approveCh <- "Y" ui.inputCh <- "a_long_password" - _, err := api.New(context.Background()) + _, err := api.New(t.Context()) if err != nil { t.Fatal(err) } @@ -143,7 +142,7 @@ func failCreateAccountWithPassword(ui *headlessUi, api *core.SignerAPI, password ui.inputCh <- password ui.inputCh <- password - addr, err := api.New(context.Background()) + addr, err := api.New(t.Context()) if err == nil { t.Fatal("Should have returned an error") } @@ -154,7 +153,7 @@ func failCreateAccountWithPassword(ui *headlessUi, api *core.SignerAPI, password func failCreateAccount(ui *headlessUi, api *core.SignerAPI, t *testing.T) { ui.approveCh <- "N" - addr, err := api.New(context.Background()) + addr, err := api.New(t.Context()) if err != core.ErrRequestDenied { t.Fatal(err) } @@ -165,7 +164,7 @@ func failCreateAccount(ui *headlessUi, api *core.SignerAPI, t *testing.T) { func list(ui *headlessUi, api *core.SignerAPI, t *testing.T) ([]common.Address, error) { ui.approveCh <- "A" - return api.List(context.Background()) + return api.List(t.Context()) } func TestNewAcc(t *testing.T) { @@ -199,7 +198,7 @@ func TestNewAcc(t *testing.T) { // Testing listing: // Listing one Account control.approveCh <- "1" - list, err := api.List(context.Background()) + list, err := api.List(t.Context()) if err != nil { t.Fatal(err) } @@ -208,7 +207,7 @@ func TestNewAcc(t *testing.T) { } // Listing denied control.approveCh <- "Nope" - list, err = api.List(context.Background()) + list, err = api.List(t.Context()) if len(list) != 0 { t.Fatalf("List should be empty") } @@ -246,7 +245,7 @@ func TestSignTx(t *testing.T) { api, control := setup(t) createAccount(control, api, t) control.approveCh <- "A" - list, err = api.List(context.Background()) + list, err = api.List(t.Context()) if err != nil { t.Fatal(err) } @@ -260,7 +259,7 @@ func TestSignTx(t *testing.T) { control.approveCh <- "Y" control.inputCh <- "wrongpassword" - res, err = api.SignTransaction(context.Background(), tx, &methodSig) + res, err = api.SignTransaction(t.Context(), tx, &methodSig) if res != nil { t.Errorf("Expected nil-response, got %v", res) } @@ -268,7 +267,7 @@ func TestSignTx(t *testing.T) { t.Errorf("Expected ErrLocked! %v", err) } control.approveCh <- "No way" - res, err = api.SignTransaction(context.Background(), tx, &methodSig) + res, err = api.SignTransaction(t.Context(), tx, &methodSig) if res != nil { t.Errorf("Expected nil-response, got %v", res) } @@ -278,22 +277,21 @@ func TestSignTx(t *testing.T) { // Sign with correct password control.approveCh <- "Y" control.inputCh <- "a_long_password" - res, err = api.SignTransaction(context.Background(), tx, &methodSig) - + res, err = api.SignTransaction(t.Context(), tx, &methodSig) if err != nil { t.Fatal(err) } parsedTx := &types.Transaction{} rlp.DecodeBytes(res.Raw, parsedTx) - //The tx should NOT be modified by the UI + // The tx should NOT be modified by the UI if parsedTx.Value().Cmp(tx.Value.ToInt()) != 0 { t.Errorf("Expected value to be unchanged, expected %v got %v", tx.Value, parsedTx.Value()) } control.approveCh <- "Y" control.inputCh <- "a_long_password" - res2, err = api.SignTransaction(context.Background(), tx, &methodSig) + res2, err = api.SignTransaction(t.Context(), tx, &methodSig) if err != nil { t.Fatal(err) } @@ -301,20 +299,20 @@ func TestSignTx(t *testing.T) { t.Error("Expected tx to be unmodified by UI") } - //The tx is modified by the UI + // The tx is modified by the UI control.approveCh <- "M" control.inputCh <- "a_long_password" - res2, err = api.SignTransaction(context.Background(), tx, &methodSig) + res2, err = api.SignTransaction(t.Context(), tx, &methodSig) if err != nil { t.Fatal(err) } parsedTx2 := &types.Transaction{} - rlp.DecodeBytes(res.Raw, parsedTx2) + rlp.DecodeBytes(res2.Raw, parsedTx2) - //The tx should be modified by the UI - if parsedTx2.Value().Cmp(tx.Value.ToInt()) != 0 { - t.Errorf("Expected value to be unchanged, got %v", parsedTx.Value()) + // The tx should be modified by the UI + if parsedTx2.Value().Cmp(tx.Value.ToInt()) == 0 { + t.Errorf("Expected value to be changed, got %v", parsedTx2.Value()) } if bytes.Equal(res.Raw, res2.Raw) { t.Error("Expected tx to be modified by UI") From 8e87b7539b26ceeac7919037e7dbc6e5e9c136b5 Mon Sep 17 00:00:00 2001 From: MozirDmitriy Date: Fri, 26 Sep 2025 16:47:58 +0300 Subject: [PATCH 05/61] trie: correct error messages for UpdateStorage operations (#32746) Fix incorrect error messages in TestVerkleTreeReadWrite and TestVerkleRollBack functions. --- trie/verkle_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/trie/verkle_test.go b/trie/verkle_test.go index f31ab02df9..1832e3db13 100644 --- a/trie/verkle_test.go +++ b/trie/verkle_test.go @@ -66,7 +66,7 @@ func TestVerkleTreeReadWrite(t *testing.T) { } for key, val := range storages[addr] { if err := tr.UpdateStorage(addr, key.Bytes(), val); err != nil { - t.Fatalf("Failed to update account, %v", err) + t.Fatalf("Failed to update storage, %v", err) } } } @@ -107,7 +107,7 @@ func TestVerkleRollBack(t *testing.T) { } for key, val := range storages[addr] { if err := tr.UpdateStorage(addr, key.Bytes(), val); err != nil { - t.Fatalf("Failed to update account, %v", err) + t.Fatalf("Failed to update storage, %v", err) } } hash := crypto.Keccak256Hash(code) From c984d9086e72d8bfbd2326c10368743bfcaec839 Mon Sep 17 00:00:00 2001 From: Dragan Milic Date: Fri, 26 Sep 2025 18:05:27 +0200 Subject: [PATCH 06/61] eth/tracers/native: add keccak256preimage tracer (#32569) Introduces a new tracer which returns the preimages of evm KECCAK256 hashes. See #32570. --------- Co-authored-by: Sina M <1591639+s1na@users.noreply.github.com> Co-authored-by: Sina Mahmoodi --- eth/tracers/native/keccak256_preimage.go | 86 ++++ eth/tracers/native/keccak256_preimage_test.go | 442 ++++++++++++++++++ 2 files changed, 528 insertions(+) create mode 100644 eth/tracers/native/keccak256_preimage.go create mode 100644 eth/tracers/native/keccak256_preimage_test.go diff --git a/eth/tracers/native/keccak256_preimage.go b/eth/tracers/native/keccak256_preimage.go new file mode 100644 index 0000000000..0c2b7e6e32 --- /dev/null +++ b/eth/tracers/native/keccak256_preimage.go @@ -0,0 +1,86 @@ +package native + +// Copyright 2021 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 . + +import ( + "encoding/json" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/core/tracing" + "github.com/ethereum/go-ethereum/core/vm" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/eth/tracers" + "github.com/ethereum/go-ethereum/eth/tracers/internal" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/params" +) + +func init() { + tracers.DefaultDirectory.Register("keccak256PreimageTracer", newKeccak256PreimageTracer, false) +} + +// keccak256PreimageTracer is a native tracer that collects preimages of all KECCAK256 operations. +// This tracer is particularly useful for analyzing smart contract execution patterns, +// especially when debugging storage access in Solidity mappings and dynamic arrays. +type keccak256PreimageTracer struct { + computedHashes map[common.Hash]hexutil.Bytes +} + +// newKeccak256PreimageTracer returns a new keccak256PreimageTracer instance. +func newKeccak256PreimageTracer(ctx *tracers.Context, cfg json.RawMessage, chainConfig *params.ChainConfig) (*tracers.Tracer, error) { + t := &keccak256PreimageTracer{ + computedHashes: make(map[common.Hash]hexutil.Bytes), + } + return &tracers.Tracer{ + Hooks: &tracing.Hooks{ + OnOpcode: t.OnOpcode, + }, + GetResult: t.GetResult, + }, nil +} + +func (t *keccak256PreimageTracer) OnOpcode(pc uint64, op byte, gas, cost uint64, scope tracing.OpContext, rData []byte, depth int, err error) { + if op == byte(vm.KECCAK256) { + sd := scope.StackData() + // it turns out that sometimes the stack is empty, evm will fail in this case, but we should not panic here + if len(sd) < 2 { + return + } + + dataOffset := internal.StackBack(sd, 0).Uint64() + dataLength := internal.StackBack(sd, 1).Uint64() + preimage, err := internal.GetMemoryCopyPadded(scope.MemoryData(), int64(dataOffset), int64(dataLength)) + if err != nil { + log.Warn("keccak256PreimageTracer: failed to copy keccak preimage from memory", "err", err) + return + } + + hash := crypto.Keccak256(preimage) + + t.computedHashes[common.Hash(hash)] = hexutil.Bytes(preimage) + } +} + +// GetResult returns the collected keccak256 preimages as a JSON object mapping hashes to preimages. +func (t *keccak256PreimageTracer) GetResult() (json.RawMessage, error) { + msg, err := json.Marshal(t.computedHashes) + if err != nil { + return nil, err + } + return msg, nil +} diff --git a/eth/tracers/native/keccak256_preimage_test.go b/eth/tracers/native/keccak256_preimage_test.go new file mode 100644 index 0000000000..b54b0cc238 --- /dev/null +++ b/eth/tracers/native/keccak256_preimage_test.go @@ -0,0 +1,442 @@ +// Copyright 2021 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 native_test + +import ( + "encoding/json" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/core/tracing" + "github.com/ethereum/go-ethereum/core/vm" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/eth/tracers" + "github.com/ethereum/go-ethereum/params" + "github.com/holiman/uint256" + "github.com/stretchr/testify/require" +) + +// mockOpContext implements tracing.OpContext for testing +type mockOpContext struct { + memory []byte + stack []uint256.Int +} + +// Ensure mockOpContext implements tracing.OpContext +var _ tracing.OpContext = (*mockOpContext)(nil) + +func (m *mockOpContext) MemoryData() []byte { + return m.memory +} + +func (m *mockOpContext) StackData() []uint256.Int { + return m.stack +} + +func (m *mockOpContext) Address() common.Address { + return common.Address{} +} + +func (m *mockOpContext) Caller() common.Address { + return common.Address{} +} + +func (m *mockOpContext) CallValue() *uint256.Int { + return uint256.NewInt(0) +} + +func (m *mockOpContext) CallInput() []byte { + return []byte{} +} + +func (m *mockOpContext) ContractCode() []byte { + return []byte{} +} + +func TestKeccak256PreimageTracerCreation(t *testing.T) { + tracer, err := tracers.DefaultDirectory.New("keccak256PreimageTracer", &tracers.Context{}, nil, params.MainnetChainConfig) + require.NoError(t, err) + require.NotNil(t, tracer) + require.NotNil(t, tracer.Hooks) + require.NotNil(t, tracer.Hooks.OnOpcode) + require.NotNil(t, tracer.GetResult) +} + +func TestKeccak256PreimageTracerInitialResult(t *testing.T) { + tracer, err := tracers.DefaultDirectory.New("keccak256PreimageTracer", &tracers.Context{}, nil, params.MainnetChainConfig) + require.NoError(t, err) + + result, err := tracer.GetResult() + require.NoError(t, err) + + var hashes map[common.Hash]hexutil.Bytes + err = json.Unmarshal(result, &hashes) + require.NoError(t, err) + require.Empty(t, hashes) +} + +func TestKeccak256PreimageTracerSingleKeccak(t *testing.T) { + tracer, err := tracers.DefaultDirectory.New("keccak256PreimageTracer", &tracers.Context{}, nil, params.MainnetChainConfig) + require.NoError(t, err) + + // Test data: "hello world" + testData := []byte("hello world") + memory := make([]byte, 32) + copy(memory, testData) + + // Create stack with offset=0, length=11 + stack := []uint256.Int{ + *uint256.NewInt(11), // length (stack[1]) + *uint256.NewInt(0), // offset (stack[0]) + } + + mockScope := &mockOpContext{ + memory: memory, + stack: stack, + } + + // Call OnOpcode with KECCAK256 + tracer.OnOpcode(0, byte(vm.KECCAK256), 0, 0, mockScope, nil, 0, nil) + + // Get result + result, err := tracer.GetResult() + require.NoError(t, err) + + var hashes map[common.Hash]hexutil.Bytes + err = json.Unmarshal(result, &hashes) + require.NoError(t, err) + + // Verify the hash and preimage + expectedHash := crypto.Keccak256Hash(testData) + require.Len(t, hashes, 1) + require.Contains(t, hashes, expectedHash) + require.Equal(t, hexutil.Bytes(testData), hashes[expectedHash]) +} + +func TestKeccak256PreimageTracerMultipleKeccak(t *testing.T) { + tracer, err := tracers.DefaultDirectory.New("keccak256PreimageTracer", &tracers.Context{}, nil, params.MainnetChainConfig) + require.NoError(t, err) + + testCases := []struct { + name string + data []byte + }{ + {"empty", []byte{}}, + {"hello", []byte("hello")}, + {"world", []byte("world")}, + {"long_data", make([]byte, 100)}, + } + + // Initialize long_data with some pattern + for i := range testCases[3].data { + testCases[3].data[i] = byte(i % 256) + } + + expectedHashes := make(map[common.Hash]hexutil.Bytes) + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + memory := make([]byte, max(len(tc.data), 1)) + copy(memory, tc.data) + + stack := []uint256.Int{ + *uint256.NewInt(uint64(len(tc.data))), // length + *uint256.NewInt(0), // offset + } + + mockScope := &mockOpContext{ + memory: memory, + stack: stack, + } + + // Call OnOpcode with KECCAK256 + tracer.OnOpcode(0, byte(vm.KECCAK256), 0, 0, mockScope, nil, 0, nil) + + expectedHash := crypto.Keccak256Hash(tc.data) + expectedHashes[expectedHash] = hexutil.Bytes(tc.data) + }) + } + + // Get final result + result, err := tracer.GetResult() + require.NoError(t, err) + + var hashes map[common.Hash]hexutil.Bytes + err = json.Unmarshal(result, &hashes) + require.NoError(t, err) + + require.Equal(t, expectedHashes, hashes) +} + +func TestKeccak256PreimageTracerNonKeccakOpcodes(t *testing.T) { + tracer, err := tracers.DefaultDirectory.New("keccak256PreimageTracer", &tracers.Context{}, nil, params.MainnetChainConfig) + require.NoError(t, err) + + testData := []byte("should not be recorded") + memory := make([]byte, 32) + copy(memory, testData) + + stack := []uint256.Int{ + *uint256.NewInt(uint64(len(testData))), + *uint256.NewInt(0), + } + + mockScope := &mockOpContext{ + memory: memory, + stack: stack, + } + + // Test various non-KECCAK256 opcodes + nonKeccakOpcodes := []vm.OpCode{ + vm.ADD, vm.MUL, vm.SUB, vm.DIV, vm.SDIV, vm.MOD, vm.SMOD, + vm.ADDMOD, vm.MULMOD, vm.EXP, vm.SIGNEXTEND, vm.SLOAD, + vm.SSTORE, vm.MLOAD, vm.MSTORE, vm.CALL, vm.RETURN, + } + + for _, opcode := range nonKeccakOpcodes { + tracer.OnOpcode(0, byte(opcode), 0, 0, mockScope, nil, 0, nil) + } + + // Get result - should be empty + result, err := tracer.GetResult() + require.NoError(t, err) + + var hashes map[common.Hash]hexutil.Bytes + err = json.Unmarshal(result, &hashes) + require.NoError(t, err) + require.Empty(t, hashes) +} + +func TestKeccak256PreimageTracerMemoryOffset(t *testing.T) { + tracer, err := tracers.DefaultDirectory.New("keccak256PreimageTracer", &tracers.Context{}, nil, params.MainnetChainConfig) + require.NoError(t, err) + + // Test data at different memory offset + prefix := []byte("prefix_data_") + testData := []byte("target_data") + memory := make([]byte, len(prefix)+len(testData)+10) + copy(memory, prefix) + copy(memory[len(prefix):], testData) + + // Stack: offset=len(prefix), length=len(testData) + stack := []uint256.Int{ + *uint256.NewInt(uint64(len(testData))), // length + *uint256.NewInt(uint64(len(prefix))), // offset + } + + mockScope := &mockOpContext{ + memory: memory, + stack: stack, + } + + // Call OnOpcode with KECCAK256 + tracer.OnOpcode(0, byte(vm.KECCAK256), 0, 0, mockScope, nil, 0, nil) + + // Get result + result, err := tracer.GetResult() + require.NoError(t, err) + + var hashes map[common.Hash]hexutil.Bytes + err = json.Unmarshal(result, &hashes) + require.NoError(t, err) + + // Verify the hash matches the target data, not the prefix + expectedHash := crypto.Keccak256Hash(testData) + require.Len(t, hashes, 1) + require.Contains(t, hashes, expectedHash) + require.Equal(t, hexutil.Bytes(testData), hashes[expectedHash]) +} + +func TestKeccak256PreimageTracerMemoryPadding(t *testing.T) { + tracer, err := tracers.DefaultDirectory.New("keccak256PreimageTracer", &tracers.Context{}, nil, params.MainnetChainConfig) + require.NoError(t, err) + + // Test data that extends beyond memory bounds (should be zero-padded) + testData := []byte("short") + memory := make([]byte, len(testData)) + copy(memory, testData) + + // Request more data than available in memory + requestedLength := len(testData) + 5 + stack := []uint256.Int{ + *uint256.NewInt(uint64(requestedLength)), // length > memory size + *uint256.NewInt(0), // offset + } + + mockScope := &mockOpContext{ + memory: memory, + stack: stack, + } + + // Call OnOpcode with KECCAK256 + tracer.OnOpcode(0, byte(vm.KECCAK256), 0, 0, mockScope, nil, 0, nil) + + // Get result + result, err := tracer.GetResult() + require.NoError(t, err) + + var hashes map[common.Hash]hexutil.Bytes + err = json.Unmarshal(result, &hashes) + require.NoError(t, err) + + // Verify the hash includes zero padding + expectedData := make([]byte, requestedLength) + copy(expectedData, testData) + // Rest is zero-padded by default + + expectedHash := crypto.Keccak256Hash(expectedData) + require.Len(t, hashes, 1) + require.Contains(t, hashes, expectedHash) + require.Equal(t, hexutil.Bytes(expectedData), hashes[expectedHash]) +} + +func TestKeccak256PreimageTracerDuplicateHashes(t *testing.T) { + tracer, err := tracers.DefaultDirectory.New("keccak256PreimageTracer", &tracers.Context{}, nil, params.MainnetChainConfig) + require.NoError(t, err) + + testData := []byte("duplicate_test") + memory := make([]byte, len(testData)) + copy(memory, testData) + + stack := []uint256.Int{ + *uint256.NewInt(uint64(len(testData))), + *uint256.NewInt(0), + } + + mockScope := &mockOpContext{ + memory: memory, + stack: stack, + } + + // Call OnOpcode with KECCAK256 multiple times with same data + for i := 0; i < 3; i++ { + tracer.OnOpcode(0, byte(vm.KECCAK256), 0, 0, mockScope, nil, 0, nil) + } + + // Get result + result, err := tracer.GetResult() + require.NoError(t, err) + + var hashes map[common.Hash]hexutil.Bytes + err = json.Unmarshal(result, &hashes) + require.NoError(t, err) + + // Should only have one entry (duplicates overwrite) + expectedHash := crypto.Keccak256Hash(testData) + require.Len(t, hashes, 1) + require.Contains(t, hashes, expectedHash) + require.Equal(t, hexutil.Bytes(testData), hashes[expectedHash]) +} + +func TestKeccak256PreimageTracerWithExecutionError(t *testing.T) { + tracer, err := tracers.DefaultDirectory.New("keccak256PreimageTracer", &tracers.Context{}, nil, params.MainnetChainConfig) + require.NoError(t, err) + + testData := []byte("error_test") + memory := make([]byte, len(testData)) + copy(memory, testData) + + stack := []uint256.Int{ + *uint256.NewInt(uint64(len(testData))), + *uint256.NewInt(0), + } + + mockScope := &mockOpContext{ + memory: memory, + stack: stack, + } + + // Call OnOpcode with KECCAK256 and an execution error + tracer.OnOpcode(0, byte(vm.KECCAK256), 0, 0, mockScope, nil, 0, vm.ErrOutOfGas) + + // Get result - should still record the hash even with execution error + result, err := tracer.GetResult() + require.NoError(t, err) + + var hashes map[common.Hash]hexutil.Bytes + err = json.Unmarshal(result, &hashes) + require.NoError(t, err) + + expectedHash := crypto.Keccak256Hash(testData) + require.Len(t, hashes, 1) + require.Contains(t, hashes, expectedHash) + require.Equal(t, hexutil.Bytes(testData), hashes[expectedHash]) +} + +func TestKeccak256PreimageTracerInsufficientStack(t *testing.T) { + tracer, err := tracers.DefaultDirectory.New("keccak256PreimageTracer", &tracers.Context{}, nil, params.MainnetChainConfig) + require.NoError(t, err) + + // Test with insufficient stack items (should cause panic, but we test it doesn't crash) + testData := []byte("test") + memory := make([]byte, len(testData)) + copy(memory, testData) + + // Stack with only one item (need 2 for KECCAK256) + stack := []uint256.Int{ + *uint256.NewInt(0), // only offset, missing length + } + + mockScope := &mockOpContext{ + memory: memory, + stack: stack, + } + + // This should not panic due to insufficient stack + tracer.OnOpcode(0, byte(vm.KECCAK256), 0, 0, mockScope, nil, 0, nil) +} + +func TestKeccak256PreimageTracerLargeData(t *testing.T) { + tracer, err := tracers.DefaultDirectory.New("keccak256PreimageTracer", &tracers.Context{}, nil, params.MainnetChainConfig) + require.NoError(t, err) + + // Test with large data + largeData := make([]byte, 1024) + for i := range largeData { + largeData[i] = byte(i % 256) + } + + memory := make([]byte, len(largeData)) + copy(memory, largeData) + + stack := []uint256.Int{ + *uint256.NewInt(uint64(len(largeData))), + *uint256.NewInt(0), + } + + mockScope := &mockOpContext{ + memory: memory, + stack: stack, + } + + // Call OnOpcode with KECCAK256 + tracer.OnOpcode(0, byte(vm.KECCAK256), 0, 0, mockScope, nil, 0, nil) + + // Get result + result, err := tracer.GetResult() + require.NoError(t, err) + + var hashes map[common.Hash]hexutil.Bytes + err = json.Unmarshal(result, &hashes) + require.NoError(t, err) + + expectedHash := crypto.Keccak256Hash(largeData) + require.Len(t, hashes, 1) + require.Contains(t, hashes, expectedHash) + require.Equal(t, hexutil.Bytes(largeData), hashes[expectedHash]) +} From b19452dc11312afa44e6fbca2f2c9a6489b0c489 Mon Sep 17 00:00:00 2001 From: Marius van der Wijden Date: Fri, 26 Sep 2025 23:39:22 +0200 Subject: [PATCH 07/61] params: add amsterdam fork config (#32687) Adds Amsterdam as fork config option. Co-authored-by: lightclient --- params/config.go | 74 +++++++++++++++++++++++++++---------------- params/forks/forks.go | 2 ++ 2 files changed, 48 insertions(+), 28 deletions(-) diff --git a/params/config.go b/params/config.go index 42a2c61ab5..0cf3198ff9 100644 --- a/params/config.go +++ b/params/config.go @@ -448,16 +448,17 @@ type ChainConfig struct { // Fork scheduling was switched from blocks to timestamps here - ShanghaiTime *uint64 `json:"shanghaiTime,omitempty"` // Shanghai switch time (nil = no fork, 0 = already on shanghai) - CancunTime *uint64 `json:"cancunTime,omitempty"` // Cancun switch time (nil = no fork, 0 = already on cancun) - PragueTime *uint64 `json:"pragueTime,omitempty"` // Prague switch time (nil = no fork, 0 = already on prague) - OsakaTime *uint64 `json:"osakaTime,omitempty"` // Osaka switch time (nil = no fork, 0 = already on osaka) - VerkleTime *uint64 `json:"verkleTime,omitempty"` // Verkle switch time (nil = no fork, 0 = already on verkle) - BPO1Time *uint64 `json:"bpo1Time,omitempty"` // BPO1 switch time (nil = no fork, 0 = already on bpo1) - BPO2Time *uint64 `json:"bpo2Time,omitempty"` // BPO2 switch time (nil = no fork, 0 = already on bpo2) - BPO3Time *uint64 `json:"bpo3Time,omitempty"` // BPO3 switch time (nil = no fork, 0 = already on bpo3) - BPO4Time *uint64 `json:"bpo4Time,omitempty"` // BPO4 switch time (nil = no fork, 0 = already on bpo4) - BPO5Time *uint64 `json:"bpo5Time,omitempty"` // BPO5 switch time (nil = no fork, 0 = already on bpo5) + ShanghaiTime *uint64 `json:"shanghaiTime,omitempty"` // Shanghai switch time (nil = no fork, 0 = already on shanghai) + CancunTime *uint64 `json:"cancunTime,omitempty"` // Cancun switch time (nil = no fork, 0 = already on cancun) + PragueTime *uint64 `json:"pragueTime,omitempty"` // Prague switch time (nil = no fork, 0 = already on prague) + OsakaTime *uint64 `json:"osakaTime,omitempty"` // Osaka switch time (nil = no fork, 0 = already on osaka) + BPO1Time *uint64 `json:"bpo1Time,omitempty"` // BPO1 switch time (nil = no fork, 0 = already on bpo1) + BPO2Time *uint64 `json:"bpo2Time,omitempty"` // BPO2 switch time (nil = no fork, 0 = already on bpo2) + BPO3Time *uint64 `json:"bpo3Time,omitempty"` // BPO3 switch time (nil = no fork, 0 = already on bpo3) + BPO4Time *uint64 `json:"bpo4Time,omitempty"` // BPO4 switch time (nil = no fork, 0 = already on bpo4) + BPO5Time *uint64 `json:"bpo5Time,omitempty"` // BPO5 switch time (nil = no fork, 0 = already on bpo5) + AmsterdamTime *uint64 `json:"amsterdamTime,omitempty"` // Amsterdam switch time (nil = no fork, 0 = already on amsterdam) + VerkleTime *uint64 `json:"verkleTime,omitempty"` // Verkle switch time (nil = no fork, 0 = already on verkle) // TerminalTotalDifficulty is the amount of total difficulty reached by // the network that triggers the consensus upgrade. @@ -575,9 +576,6 @@ func (c *ChainConfig) Description() string { if c.OsakaTime != nil { banner += fmt.Sprintf(" - Osaka: @%-10v (https://ethereum.github.io/execution-specs/src/ethereum/forks/osaka/__init__.py.html)\n", *c.OsakaTime) } - if c.VerkleTime != nil { - banner += fmt.Sprintf(" - Verkle: @%-10v\n", *c.VerkleTime) - } if c.BPO1Time != nil { banner += fmt.Sprintf(" - BPO1: @%-10v\n", *c.BPO1Time) } @@ -593,6 +591,12 @@ func (c *ChainConfig) Description() string { if c.BPO5Time != nil { banner += fmt.Sprintf(" - BPO5: @%-10v\n", *c.BPO5Time) } + if c.AmsterdamTime != nil { + banner += fmt.Sprintf(" - Amsterdam: @%-10v\n", *c.AmsterdamTime) + } + if c.VerkleTime != nil { + banner += fmt.Sprintf(" - Verkle: @%-10v\n", *c.VerkleTime) + } return banner } @@ -605,15 +609,16 @@ type BlobConfig struct { // BlobScheduleConfig determines target and max number of blobs allow per fork. type BlobScheduleConfig struct { - Cancun *BlobConfig `json:"cancun,omitempty"` - Prague *BlobConfig `json:"prague,omitempty"` - Osaka *BlobConfig `json:"osaka,omitempty"` - Verkle *BlobConfig `json:"verkle,omitempty"` - BPO1 *BlobConfig `json:"bpo1,omitempty"` - BPO2 *BlobConfig `json:"bpo2,omitempty"` - BPO3 *BlobConfig `json:"bpo3,omitempty"` - BPO4 *BlobConfig `json:"bpo4,omitempty"` - BPO5 *BlobConfig `json:"bpo5,omitempty"` + Cancun *BlobConfig `json:"cancun,omitempty"` + Prague *BlobConfig `json:"prague,omitempty"` + Osaka *BlobConfig `json:"osaka,omitempty"` + Verkle *BlobConfig `json:"verkle,omitempty"` + BPO1 *BlobConfig `json:"bpo1,omitempty"` + BPO2 *BlobConfig `json:"bpo2,omitempty"` + BPO3 *BlobConfig `json:"bpo3,omitempty"` + BPO4 *BlobConfig `json:"bpo4,omitempty"` + BPO5 *BlobConfig `json:"bpo5,omitempty"` + Amsterdam *BlobConfig `json:"amsterdam,omitempty"` } // IsHomestead returns whether num is either equal to the homestead block or greater. @@ -726,11 +731,6 @@ func (c *ChainConfig) IsOsaka(num *big.Int, time uint64) bool { return c.IsLondon(num) && isTimestampForked(c.OsakaTime, time) } -// IsVerkle returns whether time is either equal to the Verkle fork time or greater. -func (c *ChainConfig) IsVerkle(num *big.Int, time uint64) bool { - return c.IsLondon(num) && isTimestampForked(c.VerkleTime, time) -} - // IsBPO1 returns whether time is either equal to the BPO1 fork time or greater. func (c *ChainConfig) IsBPO1(num *big.Int, time uint64) bool { return c.IsLondon(num) && isTimestampForked(c.BPO1Time, time) @@ -756,6 +756,16 @@ func (c *ChainConfig) IsBPO5(num *big.Int, time uint64) bool { return c.IsLondon(num) && isTimestampForked(c.BPO5Time, time) } +// IsAmsterdam returns whether time is either equal to the Amsterdam fork time or greater. +func (c *ChainConfig) IsAmsterdam(num *big.Int, time uint64) bool { + return c.IsLondon(num) && isTimestampForked(c.AmsterdamTime, time) +} + +// IsVerkle returns whether time is either equal to the Verkle fork time or greater. +func (c *ChainConfig) IsVerkle(num *big.Int, time uint64) bool { + return c.IsLondon(num) && isTimestampForked(c.VerkleTime, time) +} + // IsVerkleGenesis checks whether the verkle fork is activated at the genesis block. // // Verkle mode is considered enabled if the verkle fork time is configured, @@ -836,6 +846,7 @@ func (c *ChainConfig) CheckConfigForkOrder() error { {name: "bpo3", timestamp: c.BPO3Time, optional: true}, {name: "bpo4", timestamp: c.BPO4Time, optional: true}, {name: "bpo5", timestamp: c.BPO5Time, optional: true}, + {name: "amsterdam", timestamp: c.AmsterdamTime, optional: true}, } { if lastFork.name != "" { switch { @@ -890,6 +901,7 @@ func (c *ChainConfig) CheckConfigForkOrder() error { {name: "bpo3", timestamp: c.BPO3Time, config: bsc.BPO3}, {name: "bpo4", timestamp: c.BPO4Time, config: bsc.BPO4}, {name: "bpo5", timestamp: c.BPO5Time, config: bsc.BPO5}, + {name: "amsterdam", timestamp: c.AmsterdamTime, config: bsc.Amsterdam}, } { if cur.config != nil { if err := cur.config.validate(); err != nil { @@ -1005,6 +1017,9 @@ func (c *ChainConfig) checkCompatible(newcfg *ChainConfig, headNumber *big.Int, if isForkTimestampIncompatible(c.BPO5Time, newcfg.BPO5Time, headTimestamp) { return newTimestampCompatError("BPO5 fork timestamp", c.BPO5Time, newcfg.BPO5Time) } + if isForkTimestampIncompatible(c.AmsterdamTime, newcfg.AmsterdamTime, headTimestamp) { + return newTimestampCompatError("Amsterdam fork timestamp", c.AmsterdamTime, newcfg.AmsterdamTime) + } return nil } @@ -1024,6 +1039,8 @@ func (c *ChainConfig) LatestFork(time uint64) forks.Fork { london := c.LondonBlock switch { + case c.IsAmsterdam(london, time): + return forks.Amsterdam case c.IsBPO5(london, time): return forks.BPO5 case c.IsBPO4(london, time): @@ -1259,7 +1276,7 @@ type Rules struct { IsByzantium, IsConstantinople, IsPetersburg, IsIstanbul bool IsBerlin, IsLondon bool IsMerge, IsShanghai, IsCancun, IsPrague, IsOsaka bool - IsVerkle bool + IsAmsterdam, IsVerkle bool } // Rules ensures c's ChainID is not nil. @@ -1289,6 +1306,7 @@ func (c *ChainConfig) Rules(num *big.Int, isMerge bool, timestamp uint64) Rules IsCancun: isMerge && c.IsCancun(num, timestamp), IsPrague: isMerge && c.IsPrague(num, timestamp), IsOsaka: isMerge && c.IsOsaka(num, timestamp), + IsAmsterdam: isMerge && c.IsAmsterdam(num, timestamp), IsVerkle: isVerkle, IsEIP4762: isVerkle, } diff --git a/params/forks/forks.go b/params/forks/forks.go index adb65c8624..641d59434b 100644 --- a/params/forks/forks.go +++ b/params/forks/forks.go @@ -45,6 +45,7 @@ const ( BPO3 BPO4 BPO5 + Amsterdam ) // String implements fmt.Stringer. @@ -82,4 +83,5 @@ var forkToString = map[Fork]string{ BPO3: "BPO3", BPO4: "BPO4", BPO5: "BPO5", + Amsterdam: "Amsterdam", } From 943a30d1ee12a482a1a3e920dccde3bbe705ce7a Mon Sep 17 00:00:00 2001 From: cui Date: Mon, 29 Sep 2025 10:17:47 +0800 Subject: [PATCH 08/61] build: remove duplicated func FileExist (#32768) --- build/ci.go | 5 +++-- internal/build/file.go | 9 --------- 2 files changed, 3 insertions(+), 11 deletions(-) diff --git a/build/ci.go b/build/ci.go index c6f4f28c87..da867a1516 100644 --- a/build/ci.go +++ b/build/ci.go @@ -57,6 +57,7 @@ import ( "time" "github.com/cespare/cp" + "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto/signify" "github.com/ethereum/go-ethereum/internal/build" "github.com/ethereum/go-ethereum/internal/download" @@ -148,7 +149,7 @@ func executablePath(name string) string { func main() { log.SetFlags(log.Lshortfile) - if !build.FileExist(filepath.Join("build", "ci.go")) { + if !common.FileExist(filepath.Join("build", "ci.go")) { log.Fatal("this script must be run from the root of the repository") } if len(os.Args) < 2 { @@ -895,7 +896,7 @@ func ppaUpload(workdir, ppa, sshUser string, files []string) { var idfile string if sshkey := getenvBase64("PPA_SSH_KEY"); len(sshkey) > 0 { idfile = filepath.Join(workdir, "sshkey") - if !build.FileExist(idfile) { + if !common.FileExist(idfile) { os.WriteFile(idfile, sshkey, 0600) } } diff --git a/internal/build/file.go b/internal/build/file.go index 7490af281e..2cd090c42c 100644 --- a/internal/build/file.go +++ b/internal/build/file.go @@ -25,15 +25,6 @@ import ( "strings" ) -// FileExist checks if a file exists at path. -func FileExist(path string) bool { - _, err := os.Stat(path) - if err != nil && os.IsNotExist(err) { - return false - } - return true -} - // HashFolder iterates all files under the given directory, computing the hash // of each. func HashFolder(folder string, exlude []string) (map[string][32]byte, error) { From 265db06242f8b47729ff8c23c482cc79f0421056 Mon Sep 17 00:00:00 2001 From: Delweng Date: Mon, 29 Sep 2025 17:56:39 +0800 Subject: [PATCH 09/61] eth/catalyst: check osaka in engine_getBlobsV1 (#32731) ref https://github.com/ethereum/execution-apis/blob/main/src/engine/osaka.md#cancun-api > Client software MUST return -38005: Unsupported fork error if the Osaka fork has been activated. --------- Signed-off-by: Delweng Co-authored-by: rjl493456442 --- eth/catalyst/api.go | 13 ++++++++++--- eth/catalyst/api_test.go | 25 +++++++++++++++++++++++++ 2 files changed, 35 insertions(+), 3 deletions(-) diff --git a/eth/catalyst/api.go b/eth/catalyst/api.go index b37c26149f..6dfe24f729 100644 --- a/eth/catalyst/api.go +++ b/eth/catalyst/api.go @@ -492,6 +492,12 @@ func (api *ConsensusAPI) getPayload(payloadID engine.PayloadID, full bool) (*eng // Client software MAY return an array of all null entries if syncing or otherwise // unable to serve blob pool data. func (api *ConsensusAPI) GetBlobsV1(hashes []common.Hash) ([]*engine.BlobAndProofV1, error) { + // Reject the request if Osaka has been activated. + // follow https://github.com/ethereum/execution-apis/blob/main/src/engine/osaka.md#cancun-api + head := api.eth.BlockChain().CurrentHeader() + if !api.checkFork(head.Time, forks.Cancun, forks.Prague) { + return nil, unsupportedForkErr("engine_getBlobsV1 is only available at Cancun/Prague fork") + } if len(hashes) > 128 { return nil, engine.TooLargeRequest.With(fmt.Errorf("requested blob count too large: %v", len(hashes))) } @@ -532,9 +538,6 @@ func (api *ConsensusAPI) GetBlobsV1(hashes []common.Hash) ([]*engine.BlobAndProo // - if the request is [A_versioned_hash_for_blob_with_blob_proof], the response // MUST be null as well. // -// Note, geth internally make the conversion from old version to new one, so the -// data will be returned normally. -// // Client software MUST support request sizes of at least 128 blob versioned // hashes. The client MUST return -38004: Too large request error if the number // of requested blobs is too large. @@ -542,6 +545,10 @@ func (api *ConsensusAPI) GetBlobsV1(hashes []common.Hash) ([]*engine.BlobAndProo // Client software MUST return null if syncing or otherwise unable to serve // blob pool data. func (api *ConsensusAPI) GetBlobsV2(hashes []common.Hash) ([]*engine.BlobAndProofV2, error) { + head := api.eth.BlockChain().CurrentHeader() + if api.config().LatestFork(head.Time) < forks.Osaka { + return nil, unsupportedForkErr("engine_getBlobsV2 is not available before Osaka fork") + } if len(hashes) > 128 { return nil, engine.TooLargeRequest.With(fmt.Errorf("requested blob count too large: %v", len(hashes))) } diff --git a/eth/catalyst/api_test.go b/eth/catalyst/api_test.go index 659280bf3b..a29fee1a06 100644 --- a/eth/catalyst/api_test.go +++ b/eth/catalyst/api_test.go @@ -1991,6 +1991,31 @@ func TestGetBlobsV1(t *testing.T) { } } +func TestGetBlobsV1AfterOsakaFork(t *testing.T) { + genesis := &core.Genesis{ + Config: params.MergedTestChainConfig, + Alloc: types.GenesisAlloc{testAddr: {Balance: testBalance}}, + Difficulty: common.Big0, + Timestamp: 1, // Timestamp > 0 to ensure Osaka fork is active + } + n, ethServ := startEthService(t, genesis, nil) + defer n.Close() + + var engineErr *engine.EngineAPIError + api := newConsensusAPIWithoutHeartbeat(ethServ) + _, err := api.GetBlobsV1([]common.Hash{testrand.Hash()}) + if !errors.As(err, &engineErr) { + t.Fatalf("Unexpected error: %T", err) + } else { + if engineErr.ErrorCode() != -38005 { + t.Fatalf("Expected error code -38005, got %d", engineErr.ErrorCode()) + } + if engineErr.Error() != "Unsupported fork" { + t.Fatalf("Expected error message 'Unsupported fork', got '%s'", engineErr.Error()) + } + } +} + func TestGetBlobsV2(t *testing.T) { n, api := newGetBlobEnv(t, 1) defer n.Close() From c5a1c35cfbce5b2ba6840add3acd13e3a652ab07 Mon Sep 17 00:00:00 2001 From: VolodymyrBg Date: Mon, 29 Sep 2025 13:23:43 +0300 Subject: [PATCH 10/61] trie: fix error message in test (#32772) Fixes an error message in TestReplication --- trie/trie_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/trie/trie_test.go b/trie/trie_test.go index 22c3494f47..b8b8edb33e 100644 --- a/trie/trie_test.go +++ b/trie/trie_test.go @@ -326,7 +326,7 @@ func TestReplication(t *testing.T) { updateString(trie2, val.k, val.v) } if trie2.Hash() != hash { - t.Errorf("root failure. expected %x got %x", hash, hash) + t.Errorf("root failure. expected %x got %x", hash, trie2.Hash()) } } From 4b080208ea68d5640c9873e58357f2952c725208 Mon Sep 17 00:00:00 2001 From: GarmashAlex Date: Mon, 29 Sep 2025 13:31:00 +0300 Subject: [PATCH 11/61] internal/ethapi: remove redundant check in test (#32760) Removes a redundant check in TestCreateAccessListWithStateOverrides --- internal/ethapi/api_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/ethapi/api_test.go b/internal/ethapi/api_test.go index c0a8fe9a58..2e0b1c3bc0 100644 --- a/internal/ethapi/api_test.go +++ b/internal/ethapi/api_test.go @@ -3746,8 +3746,8 @@ func TestCreateAccessListWithStateOverrides(t *testing.T) { if err != nil { t.Fatalf("Failed to create access list: %v", err) } - if err != nil || result == nil { - t.Fatalf("Failed to create access list: %v", err) + if result == nil { + t.Fatalf("Failed to create access list: result is nil") } require.NotNil(t, result.Accesslist) From 46b7e78cc02f36e4c472f7196316a73f3c069cad Mon Sep 17 00:00:00 2001 From: CPerezz <37264926+CPerezz@users.noreply.github.com> Date: Mon, 29 Sep 2025 12:51:46 +0200 Subject: [PATCH 12/61] cmd/evm/internal/t8ntool: panic on database corruption (#32776) These functions were previously ignoring the error returned by both `statedb.Commit()` and the subsequent `state.New()`, which could silently fail and cause panics later when the `statedb` is used. This change adds proper error checking and panics with a descriptive error message if state creation fails. While unlikely in normal operation, this can occur if there are database corruption issues or if invalid root hashes are provided, making debugging significantly easier when such issues do occur. This issue was encountered and fixed in https://github.com/gballet/go-ethereum/pull/552 where the error handling proved essential for debugging cc: @gballet as this was discussed in a call already. --- cmd/evm/internal/t8ntool/execution.go | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/cmd/evm/internal/t8ntool/execution.go b/cmd/evm/internal/t8ntool/execution.go index f1e65afe9c..5303d432fb 100644 --- a/cmd/evm/internal/t8ntool/execution.go +++ b/cmd/evm/internal/t8ntool/execution.go @@ -374,7 +374,10 @@ func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig, func MakePreState(db ethdb.Database, accounts types.GenesisAlloc) *state.StateDB { tdb := triedb.NewDatabase(db, &triedb.Config{Preimages: true}) sdb := state.NewDatabase(tdb, nil) - statedb, _ := state.New(types.EmptyRootHash, sdb) + statedb, err := state.New(types.EmptyRootHash, sdb) + if err != nil { + panic(fmt.Errorf("failed to create initial state: %v", err)) + } for addr, a := range accounts { statedb.SetCode(addr, a.Code, tracing.CodeChangeUnspecified) statedb.SetNonce(addr, a.Nonce, tracing.NonceChangeGenesis) @@ -384,8 +387,14 @@ func MakePreState(db ethdb.Database, accounts types.GenesisAlloc) *state.StateDB } } // Commit and re-open to start with a clean state. - root, _ := statedb.Commit(0, false, false) - statedb, _ = state.New(root, sdb) + root, err := statedb.Commit(0, false, false) + if err != nil { + panic(fmt.Errorf("failed to commit initial state: %v", err)) + } + statedb, err = state.New(root, sdb) + if err != nil { + panic(fmt.Errorf("failed to reopen state after commit: %v", err)) + } return statedb } From ea28346f91b65c9882e942d2fcad9cdbaa09d706 Mon Sep 17 00:00:00 2001 From: lightclient <14004106+lightclient@users.noreply.github.com> Date: Mon, 29 Sep 2025 05:44:04 -0600 Subject: [PATCH 13/61] params: fix bpo config comments (#32755) Looks like we forgot to update names when copying. --- params/config.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/params/config.go b/params/config.go index 0cf3198ff9..ff27b69259 100644 --- a/params/config.go +++ b/params/config.go @@ -377,25 +377,25 @@ var ( Max: 9, UpdateFraction: 5007716, } - // DefaultBPO1BlobConfig is the default blob configuration for the Osaka fork. + // DefaultBPO1BlobConfig is the default blob configuration for the BPO1 fork. DefaultBPO1BlobConfig = &BlobConfig{ Target: 10, Max: 15, UpdateFraction: 8346193, } - // DefaultBPO1BlobConfig is the default blob configuration for the Osaka fork. + // DefaultBPO2BlobConfig is the default blob configuration for the BPO2 fork. DefaultBPO2BlobConfig = &BlobConfig{ Target: 14, Max: 21, UpdateFraction: 11684671, } - // DefaultBPO1BlobConfig is the default blob configuration for the Osaka fork. + // DefaultBPO3BlobConfig is the default blob configuration for the BPO3 fork. DefaultBPO3BlobConfig = &BlobConfig{ Target: 21, Max: 32, UpdateFraction: 20609697, } - // DefaultBPO1BlobConfig is the default blob configuration for the Osaka fork. + // DefaultBPO4BlobConfig is the default blob configuration for the BPO4 fork. DefaultBPO4BlobConfig = &BlobConfig{ Target: 14, Max: 21, From 1cfe624d03e445715565f17a355687a3acefdb63 Mon Sep 17 00:00:00 2001 From: VolodymyrBg Date: Mon, 29 Sep 2025 15:45:00 +0300 Subject: [PATCH 14/61] core/rawdb: update comments (#32668) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Replace outdated NewFreezer doc that referenced map[string]bool/snappy toggle with accurate description of -map[string]freezerTableConfig (noSnappy, prunable). - Fix misleading field comment on freezerTable.config that spoke as if it were a boolean (“if true”), clarifying it’s a struct and noting compression is non-retroactive. --- core/rawdb/freezer.go | 5 +++-- core/rawdb/freezer_table.go | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/core/rawdb/freezer.go b/core/rawdb/freezer.go index a9600c1eef..98ad174ce0 100644 --- a/core/rawdb/freezer.go +++ b/core/rawdb/freezer.go @@ -76,8 +76,9 @@ type Freezer struct { // NewFreezer creates a freezer instance for maintaining immutable ordered // data according to the given parameters. // -// The 'tables' argument defines the data tables. If the value of a map -// entry is true, snappy compression is disabled for the table. +// The 'tables' argument defines the freezer tables and their configuration. +// Each value is a freezerTableConfig specifying whether snappy compression is +// disabled (noSnappy) and whether the table is prunable (prunable). func NewFreezer(datadir string, namespace string, readonly bool, maxTableSize uint32, tables map[string]freezerTableConfig) (*Freezer, error) { // Create the initial freezer object var ( diff --git a/core/rawdb/freezer_table.go b/core/rawdb/freezer_table.go index 19c40cc16e..d3a29a73c6 100644 --- a/core/rawdb/freezer_table.go +++ b/core/rawdb/freezer_table.go @@ -100,7 +100,7 @@ type freezerTable struct { // should never be lower than itemOffset. itemHidden atomic.Uint64 - config freezerTableConfig // if true, disables snappy compression. Note: does not work retroactively + config freezerTableConfig // table configuration (compression, prunability). Note: compression flag does not apply retroactively to existing files readonly bool maxFileSize uint32 // Max file size for data-files name string From 891bbad9ce219b8afa4790a3e5cae066577cfb08 Mon Sep 17 00:00:00 2001 From: futreall <86553580+futreall@users.noreply.github.com> Date: Mon, 29 Sep 2025 15:09:03 +0200 Subject: [PATCH 15/61] params: implement String() method for ChainConfig (#32766) Fixes issue #32762 where ChainConfig logging displays pointer addresses instead of actual timestamp values for fork activation times. Before: ShanghaiTime:(*uint64)(0xc000373fb0), CancunTime:(*uint64)(0xc000373fb8) After: ShanghaiTime: 1681338455, CancunTime: 1710338135, VerkleTime: nil The String() method properly dereferences timestamp pointers and handles nil values for unset fork times, making logs more readable and useful for debugging chain configuration issues. --------- Co-authored-by: rjl493456442 --- params/config.go | 90 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 90 insertions(+) diff --git a/params/config.go b/params/config.go index ff27b69259..b441d60661 100644 --- a/params/config.go +++ b/params/config.go @@ -504,6 +504,96 @@ func (c CliqueConfig) String() string { return fmt.Sprintf("clique(period: %d, epoch: %d)", c.Period, c.Epoch) } +// String implements the fmt.Stringer interface, returning a string representation +// of ChainConfig. +func (c *ChainConfig) String() string { + result := fmt.Sprintf("ChainConfig{ChainID: %v", c.ChainID) + + // Add block-based forks + if c.HomesteadBlock != nil { + result += fmt.Sprintf(", HomesteadBlock: %v", c.HomesteadBlock) + } + if c.DAOForkBlock != nil { + result += fmt.Sprintf(", DAOForkBlock: %v", c.DAOForkBlock) + } + if c.EIP150Block != nil { + result += fmt.Sprintf(", EIP150Block: %v", c.EIP150Block) + } + if c.EIP155Block != nil { + result += fmt.Sprintf(", EIP155Block: %v", c.EIP155Block) + } + if c.EIP158Block != nil { + result += fmt.Sprintf(", EIP158Block: %v", c.EIP158Block) + } + if c.ByzantiumBlock != nil { + result += fmt.Sprintf(", ByzantiumBlock: %v", c.ByzantiumBlock) + } + if c.ConstantinopleBlock != nil { + result += fmt.Sprintf(", ConstantinopleBlock: %v", c.ConstantinopleBlock) + } + if c.PetersburgBlock != nil { + result += fmt.Sprintf(", PetersburgBlock: %v", c.PetersburgBlock) + } + if c.IstanbulBlock != nil { + result += fmt.Sprintf(", IstanbulBlock: %v", c.IstanbulBlock) + } + if c.MuirGlacierBlock != nil { + result += fmt.Sprintf(", MuirGlacierBlock: %v", c.MuirGlacierBlock) + } + if c.BerlinBlock != nil { + result += fmt.Sprintf(", BerlinBlock: %v", c.BerlinBlock) + } + if c.LondonBlock != nil { + result += fmt.Sprintf(", LondonBlock: %v", c.LondonBlock) + } + if c.ArrowGlacierBlock != nil { + result += fmt.Sprintf(", ArrowGlacierBlock: %v", c.ArrowGlacierBlock) + } + if c.GrayGlacierBlock != nil { + result += fmt.Sprintf(", GrayGlacierBlock: %v", c.GrayGlacierBlock) + } + if c.MergeNetsplitBlock != nil { + result += fmt.Sprintf(", MergeNetsplitBlock: %v", c.MergeNetsplitBlock) + } + + // Add timestamp-based forks + if c.ShanghaiTime != nil { + result += fmt.Sprintf(", ShanghaiTime: %v", *c.ShanghaiTime) + } + if c.CancunTime != nil { + result += fmt.Sprintf(", CancunTime: %v", *c.CancunTime) + } + if c.PragueTime != nil { + result += fmt.Sprintf(", PragueTime: %v", *c.PragueTime) + } + if c.OsakaTime != nil { + result += fmt.Sprintf(", OsakaTime: %v", *c.OsakaTime) + } + if c.BPO1Time != nil { + result += fmt.Sprintf(", BPO1Time: %v", *c.BPO1Time) + } + if c.BPO2Time != nil { + result += fmt.Sprintf(", BPO2Time: %v", *c.BPO2Time) + } + if c.BPO3Time != nil { + result += fmt.Sprintf(", BPO3Time: %v", *c.BPO3Time) + } + if c.BPO4Time != nil { + result += fmt.Sprintf(", BPO4Time: %v", *c.BPO4Time) + } + if c.BPO5Time != nil { + result += fmt.Sprintf(", BPO5Time: %v", *c.BPO5Time) + } + if c.AmsterdamTime != nil { + result += fmt.Sprintf(", AmsterdamTime: %v", *c.AmsterdamTime) + } + if c.VerkleTime != nil { + result += fmt.Sprintf(", VerkleTime: %v", *c.VerkleTime) + } + result += "}" + return result +} + // Description returns a human-readable description of ChainConfig. func (c *ChainConfig) Description() string { var banner string From 01d0ce0bf156e52a62699b977288aecfa0366833 Mon Sep 17 00:00:00 2001 From: rjl493456442 Date: Tue, 30 Sep 2025 10:05:43 +0800 Subject: [PATCH 16/61] params: add blob config information in the banner (#32771) Extend the chain banner with blob config information. Co-authored-by: Felix Lange --- params/config.go | 63 +++++++++++++++++++++++++++--------------------- 1 file changed, 35 insertions(+), 28 deletions(-) diff --git a/params/config.go b/params/config.go index b441d60661..4e885cbdd4 100644 --- a/params/config.go +++ b/params/config.go @@ -618,34 +618,32 @@ func (c *ChainConfig) Description() string { // makes sense for mainnet should be optional at printing to avoid bloating // the output for testnets and private networks. banner += "Pre-Merge hard forks (block based):\n" - banner += fmt.Sprintf(" - Homestead: #%-8v (https://ethereum.github.io/execution-specs/src/ethereum/forks/homestead/__init__.py.html)\n", c.HomesteadBlock) + banner += fmt.Sprintf(" - Homestead: #%-8v\n", c.HomesteadBlock) if c.DAOForkBlock != nil { - banner += fmt.Sprintf(" - DAO Fork: #%-8v (https://ethereum.github.io/execution-specs/src/ethereum/forks/dao_fork/__init__.py.html)\n", c.DAOForkBlock) - } - banner += fmt.Sprintf(" - Tangerine Whistle (EIP 150): #%-8v (https://ethereum.github.io/execution-specs/src/ethereum/forks/tangerine_whistle/__init__.py.html)\n", c.EIP150Block) - banner += fmt.Sprintf(" - Spurious Dragon/1 (EIP 155): #%-8v (https://ethereum.github.io/execution-specs/src/ethereum/forks/spurious_dragon/__init__.py.html)\n", c.EIP155Block) - banner += fmt.Sprintf(" - Spurious Dragon/2 (EIP 158): #%-8v (https://ethereum.github.io/execution-specs/src/ethereum/forks/spurious_dragon/__init__.py.html)\n", c.EIP155Block) - banner += fmt.Sprintf(" - Byzantium: #%-8v (https://ethereum.github.io/execution-specs/src/ethereum/forks/byzantium/__init__.py.html)\n", c.ByzantiumBlock) - banner += fmt.Sprintf(" - Constantinople: #%-8v (https://ethereum.github.io/execution-specs/src/ethereum/forks/constantinople/__init__.py.html)\n", c.ConstantinopleBlock) - banner += fmt.Sprintf(" - Petersburg: #%-8v (https://ethereum.github.io/execution-specs/src/ethereum/forks/constantinople/__init__.py.html)\n", c.PetersburgBlock) - banner += fmt.Sprintf(" - Istanbul: #%-8v (https://ethereum.github.io/execution-specs/src/ethereum/forks/istanbul/__init__.py.html)\n", c.IstanbulBlock) + banner += fmt.Sprintf(" - DAO Fork: #%-8v\n", c.DAOForkBlock) + } + banner += fmt.Sprintf(" - Tangerine Whistle (EIP 150): #%-8v\n", c.EIP150Block) + banner += fmt.Sprintf(" - Spurious Dragon/1 (EIP 155): #%-8v\n", c.EIP155Block) + banner += fmt.Sprintf(" - Spurious Dragon/2 (EIP 158): #%-8v\n", c.EIP155Block) + banner += fmt.Sprintf(" - Byzantium: #%-8v\n", c.ByzantiumBlock) + banner += fmt.Sprintf(" - Constantinople: #%-8v\n", c.ConstantinopleBlock) + banner += fmt.Sprintf(" - Petersburg: #%-8v\n", c.PetersburgBlock) + banner += fmt.Sprintf(" - Istanbul: #%-8v\n", c.IstanbulBlock) if c.MuirGlacierBlock != nil { - banner += fmt.Sprintf(" - Muir Glacier: #%-8v (https://ethereum.github.io/execution-specs/src/ethereum/forks/muir_glacier/__init__.py.html)\n", c.MuirGlacierBlock) + banner += fmt.Sprintf(" - Muir Glacier: #%-8v\n", c.MuirGlacierBlock) } - banner += fmt.Sprintf(" - Berlin: #%-8v (https://ethereum.github.io/execution-specs/src/ethereum/forks/berlin/__init__.py.html)\n", c.BerlinBlock) - banner += fmt.Sprintf(" - London: #%-8v (https://ethereum.github.io/execution-specs/src/ethereum/forks/london/__init__.py.html)\n", c.LondonBlock) + banner += fmt.Sprintf(" - Berlin: #%-8v\n", c.BerlinBlock) + banner += fmt.Sprintf(" - London: #%-8v\n", c.LondonBlock) if c.ArrowGlacierBlock != nil { - banner += fmt.Sprintf(" - Arrow Glacier: #%-8v (https://ethereum.github.io/execution-specs/src/ethereum/forks/arrow_glacier/__init__.py.html)\n", c.ArrowGlacierBlock) + banner += fmt.Sprintf(" - Arrow Glacier: #%-8v\n", c.ArrowGlacierBlock) } if c.GrayGlacierBlock != nil { - banner += fmt.Sprintf(" - Gray Glacier: #%-8v (https://ethereum.github.io/execution-specs/src/ethereum/forks/gray_glacier/__init__.py.html)\n", c.GrayGlacierBlock) + banner += fmt.Sprintf(" - Gray Glacier: #%-8v\n", c.GrayGlacierBlock) } banner += "\n" // Add a special section for the merge as it's non-obvious banner += "Merge configured:\n" - banner += " - Hard-fork specification: https://ethereum.github.io/execution-specs/src/ethereum/forks/paris/__init__.py.html\n" - banner += " - Network known to be merged\n" banner += fmt.Sprintf(" - Total terminal difficulty: %v\n", c.TerminalTotalDifficulty) if c.MergeNetsplitBlock != nil { banner += fmt.Sprintf(" - Merge netsplit block: #%-8v\n", c.MergeNetsplitBlock) @@ -655,38 +653,39 @@ func (c *ChainConfig) Description() string { // Create a list of forks post-merge banner += "Post-Merge hard forks (timestamp based):\n" if c.ShanghaiTime != nil { - banner += fmt.Sprintf(" - Shanghai: @%-10v (https://ethereum.github.io/execution-specs/src/ethereum/forks/shanghai/__init__.py.html)\n", *c.ShanghaiTime) + banner += fmt.Sprintf(" - Shanghai: @%-10v\n", *c.ShanghaiTime) } if c.CancunTime != nil { - banner += fmt.Sprintf(" - Cancun: @%-10v (https://ethereum.github.io/execution-specs/src/ethereum/forks/cancun/__init__.py.html)\n", *c.CancunTime) + banner += fmt.Sprintf(" - Cancun: @%-10v blob: (%s)\n", *c.CancunTime, c.BlobScheduleConfig.Cancun) } if c.PragueTime != nil { - banner += fmt.Sprintf(" - Prague: @%-10v (https://ethereum.github.io/execution-specs/src/ethereum/forks/prague/__init__.py.html)\n", *c.PragueTime) + banner += fmt.Sprintf(" - Prague: @%-10v blob: (%s)\n", *c.PragueTime, c.BlobScheduleConfig.Prague) } if c.OsakaTime != nil { - banner += fmt.Sprintf(" - Osaka: @%-10v (https://ethereum.github.io/execution-specs/src/ethereum/forks/osaka/__init__.py.html)\n", *c.OsakaTime) + banner += fmt.Sprintf(" - Osaka: @%-10v blob: (%s)\n", *c.OsakaTime, c.BlobScheduleConfig.Osaka) } if c.BPO1Time != nil { - banner += fmt.Sprintf(" - BPO1: @%-10v\n", *c.BPO1Time) + banner += fmt.Sprintf(" - BPO1: @%-10v blob: (%s)\n", *c.BPO1Time, c.BlobScheduleConfig.BPO1) } if c.BPO2Time != nil { - banner += fmt.Sprintf(" - BPO2: @%-10v\n", *c.BPO2Time) + banner += fmt.Sprintf(" - BPO2: @%-10v blob: (%s)\n", *c.BPO2Time, c.BlobScheduleConfig.BPO2) } if c.BPO3Time != nil { - banner += fmt.Sprintf(" - BPO3: @%-10v\n", *c.BPO3Time) + banner += fmt.Sprintf(" - BPO3: @%-10v blob: (%s)\n", *c.BPO3Time, c.BlobScheduleConfig.BPO3) } if c.BPO4Time != nil { - banner += fmt.Sprintf(" - BPO4: @%-10v\n", *c.BPO4Time) + banner += fmt.Sprintf(" - BPO4: @%-10v blob: (%s)\n", *c.BPO4Time, c.BlobScheduleConfig.BPO4) } if c.BPO5Time != nil { - banner += fmt.Sprintf(" - BPO5: @%-10v\n", *c.BPO5Time) + banner += fmt.Sprintf(" - BPO5: @%-10v blob: (%s)\n", *c.BPO5Time, c.BlobScheduleConfig.BPO5) } if c.AmsterdamTime != nil { - banner += fmt.Sprintf(" - Amsterdam: @%-10v\n", *c.AmsterdamTime) + banner += fmt.Sprintf(" - Amsterdam: @%-10v blob: (%s)\n", *c.AmsterdamTime, c.BlobScheduleConfig.Amsterdam) } if c.VerkleTime != nil { - banner += fmt.Sprintf(" - Verkle: @%-10v\n", *c.VerkleTime) + banner += fmt.Sprintf(" - Verkle: @%-10v blob: (%s)\n", *c.VerkleTime, c.BlobScheduleConfig.Verkle) } + banner += fmt.Sprintf("\nAll fork specifications can be found at https://ethereum.github.io/execution-specs/src/ethereum/forks/\n") return banner } @@ -697,6 +696,14 @@ type BlobConfig struct { UpdateFraction uint64 `json:"baseFeeUpdateFraction"` } +// String implement fmt.Stringer, returning string format blob config. +func (bc *BlobConfig) String() string { + if bc == nil { + return "nil" + } + return fmt.Sprintf("target: %d, max: %d, fraction: %d", bc.Target, bc.Max, bc.UpdateFraction) +} + // BlobScheduleConfig determines target and max number of blobs allow per fork. type BlobScheduleConfig struct { Cancun *BlobConfig `json:"cancun,omitempty"` From c1e9d78f1f8339b8dafed994530fb6fd231c48b0 Mon Sep 17 00:00:00 2001 From: MozirDmitriy Date: Tue, 30 Sep 2025 05:07:54 +0300 Subject: [PATCH 17/61] core/txpool: remove unused signer field from TxPool (#32787) The TxPool.signer field was never read and each subpool (legacy/blob) maintains its own signer instance. This field remained after txpool refactoring into subpools and is dead code. Removing it reduces confusion and simplifies the constructor. --- core/txpool/txpool.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/core/txpool/txpool.go b/core/txpool/txpool.go index b5470cd7fc..437861efca 100644 --- a/core/txpool/txpool.go +++ b/core/txpool/txpool.go @@ -65,7 +65,6 @@ type BlockChain interface { type TxPool struct { subpools []SubPool // List of subpools for specialized transaction handling chain BlockChain - signer types.Signer stateLock sync.RWMutex // The lock for protecting state instance state *state.StateDB // Current state at the blockchain head @@ -98,7 +97,6 @@ func New(gasTip uint64, chain BlockChain, subpools []SubPool) (*TxPool, error) { pool := &TxPool{ subpools: subpools, chain: chain, - signer: types.LatestSigner(chain.Config()), state: statedb, quit: make(chan chan error), term: make(chan struct{}), From 2037c53e7a0104b33b996ef4c705a1928df207a4 Mon Sep 17 00:00:00 2001 From: Galoretka Date: Tue, 30 Sep 2025 05:11:09 +0300 Subject: [PATCH 18/61] core/state: correct expected value in TestMessageCallGas (#32780) --- core/state/access_events_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/state/access_events_test.go b/core/state/access_events_test.go index 5e1fee767c..e80859a0b4 100644 --- a/core/state/access_events_test.go +++ b/core/state/access_events_test.go @@ -131,7 +131,7 @@ func TestMessageCallGas(t *testing.T) { } gas = ae.CodeHashGas(testAddr, false, math.MaxUint64, false) if gas != params.WitnessChunkReadCost { - t.Fatalf("incorrect gas computed, got %d, want %d", gas, 0) + t.Fatalf("incorrect gas computed, got %d, want %d", gas, params.WitnessChunkReadCost) } // Check warm read cost From 6f8e28b4aab10c07fffef02823810a1dfaa06617 Mon Sep 17 00:00:00 2001 From: Guillaume Ballet <3272758+gballet@users.noreply.github.com> Date: Tue, 30 Sep 2025 12:50:20 +0200 Subject: [PATCH 19/61] go.mod, cmd/keeper/go.mod: upgrade victoria metrics dependency (#32720) This is required for geth to compile to WASM. --- cmd/keeper/go.mod | 4 ++-- cmd/keeper/go.sum | 14 ++++---------- go.mod | 4 ++-- go.sum | 10 ++++------ 4 files changed, 12 insertions(+), 20 deletions(-) diff --git a/cmd/keeper/go.mod b/cmd/keeper/go.mod index d1649da43f..16094d16b1 100644 --- a/cmd/keeper/go.mod +++ b/cmd/keeper/go.mod @@ -9,7 +9,7 @@ require ( require ( github.com/StackExchange/wmi v1.2.1 // indirect - github.com/VictoriaMetrics/fastcache v1.12.2 // indirect + github.com/VictoriaMetrics/fastcache v1.13.0 // indirect github.com/bits-and-blooms/bitset v1.20.0 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/consensys/gnark-crypto v0.18.0 // indirect @@ -24,7 +24,7 @@ require ( github.com/ferranbt/fastssz v0.1.4 // indirect github.com/go-ole/go-ole v1.3.0 // indirect github.com/gofrs/flock v0.12.1 // indirect - github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb // indirect + github.com/golang/snappy v1.0.0 // indirect github.com/holiman/bloomfilter/v2 v2.0.3 // indirect github.com/holiman/uint256 v1.3.2 // indirect github.com/klauspost/cpuid/v2 v2.0.9 // indirect diff --git a/cmd/keeper/go.sum b/cmd/keeper/go.sum index e3bc204ba8..3eaef469dc 100644 --- a/cmd/keeper/go.sum +++ b/cmd/keeper/go.sum @@ -2,15 +2,14 @@ github.com/DataDog/zstd v1.4.5 h1:EndNeuB0l9syBZhut0wns3gV1hL8zX8LIu6ZiVHWLIQ= github.com/DataDog/zstd v1.4.5/go.mod h1:1jcaCB/ufaK+sKp1NBhlGmpz41jOoPQ35bpF36t7BBo= github.com/StackExchange/wmi v1.2.1 h1:VIkavFPXSjcnS+O8yTq7NI32k0R5Aj+v39y29VYDOSA= github.com/StackExchange/wmi v1.2.1/go.mod h1:rcmrprowKIVzvc+NUiLncP2uuArMWLCbu9SBzvHz7e8= -github.com/VictoriaMetrics/fastcache v1.12.2 h1:N0y9ASrJ0F6h0QaC3o6uJb3NIZ9VKLjCM7NQbSmF7WI= -github.com/VictoriaMetrics/fastcache v1.12.2/go.mod h1:AmC+Nzz1+3G2eCPapF6UcsnkThDcMsQicp4xDukwJYI= +github.com/VictoriaMetrics/fastcache v1.13.0 h1:AW4mheMR5Vd9FkAPUv+NH6Nhw+fmbTMGMsNAoA/+4G0= +github.com/VictoriaMetrics/fastcache v1.13.0/go.mod h1:hHXhl4DA2fTL2HTZDJFXWgW0LNjo6B+4aj2Wmng3TjU= github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156 h1:eMwmnE/GDgah4HI848JfFxHt+iPb26b4zyfspmqY0/8= github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156/go.mod h1:Cb/ax3seSYIx7SuZdm2G2xzfwmv3TPSk2ucNfQESPXM= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/bits-and-blooms/bitset v1.20.0 h1:2F+rfL86jE2d/bmw7OhqUg2Sj/1rURkBn3MdfoPyRVU= github.com/bits-and-blooms/bitset v1.20.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8= -github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cockroachdb/errors v1.11.3 h1:5bA+k2Y6r+oz/6Z/RFlNeVCesGARKuC6YymtcDrbC/I= @@ -31,7 +30,6 @@ github.com/crate-crypto/go-eth-kzg v1.4.0 h1:WzDGjHk4gFg6YzV0rJOAsTK4z3Qkz5jd4RE github.com/crate-crypto/go-eth-kzg v1.4.0/go.mod h1:J9/u5sWfznSObptgfa92Jq8rTswn6ahQWEuiLHOjCUI= github.com/crate-crypto/go-ipa v0.0.0-20240724233137-53bbb0ceb27a h1:W8mUrRp6NOVl3J+MYp5kPMoUZPp7aOYHtaua31lwRHg= github.com/crate-crypto/go-ipa v0.0.0-20240724233137-53bbb0ceb27a/go.mod h1:sTwzHBvIzm2RfVCGNEBZgRyjwK40bVoun3ZnGOCafNM= -github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/deckarep/golang-set/v2 v2.6.0 h1:XfcQbWM1LlMB8BsJ8N9vW5ehnnPVIw0je80NsVHagjM= @@ -61,9 +59,8 @@ github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= -github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= -github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb h1:PBC98N2aIaM3XXiurYmW7fx4GZkL8feAMVq7nEjURHk= -github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/golang/snappy v1.0.0 h1:Oy607GVXHs7RtbggtPBnr2RmDArIsAefDwvrdWvRhGs= +github.com/golang/snappy v1.0.0/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/holiman/bloomfilter/v2 v2.0.3 h1:73e0e/V0tCydx14a0SCYS/EWCxgwLZ18CZcZKVu0fao= github.com/holiman/bloomfilter/v2 v2.0.3/go.mod h1:zpoh+gs7qcpqrHr3dB55AMiJwo0iURXE7ZOP9L9hSkA= github.com/holiman/uint256 v1.3.2 h1:a9EgMPSC1AAaj1SZL5zIQD3WbwTuHrMGOerLjGmM/TA= @@ -112,8 +109,6 @@ github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible h1:Bn1aCHHRnjv4Bl16T8rcaFjYSrGrIZvpiGO6P3Q4GpU= github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= -github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/supranational/blst v0.3.16-0.20250831170142-f48500c1fdbe h1:nbdqkIGOGfUAD54q1s2YBcBz/WcsxCO9HUQ4aGV5hUw= @@ -134,7 +129,6 @@ golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k= golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= diff --git a/go.mod b/go.mod index 03cdf3bb2d..c91cc81d21 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ go 1.24.0 require ( github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.2.0 github.com/Microsoft/go-winio v0.6.2 - github.com/VictoriaMetrics/fastcache v1.12.2 + github.com/VictoriaMetrics/fastcache v1.13.0 github.com/aws/aws-sdk-go-v2 v1.21.2 github.com/aws/aws-sdk-go-v2/config v1.18.45 github.com/aws/aws-sdk-go-v2/credentials v1.13.43 @@ -31,7 +31,7 @@ require ( github.com/gballet/go-libpcsclite v0.0.0-20190607065134-2772fd86a8ff github.com/gofrs/flock v0.12.1 github.com/golang-jwt/jwt/v4 v4.5.2 - github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb + github.com/golang/snappy v1.0.0 github.com/google/gofuzz v1.2.0 github.com/google/uuid v1.3.0 github.com/gorilla/websocket v1.4.2 diff --git a/go.sum b/go.sum index 764cfdb668..779bcde846 100644 --- a/go.sum +++ b/go.sum @@ -16,8 +16,8 @@ github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERo github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= github.com/StackExchange/wmi v1.2.1 h1:VIkavFPXSjcnS+O8yTq7NI32k0R5Aj+v39y29VYDOSA= github.com/StackExchange/wmi v1.2.1/go.mod h1:rcmrprowKIVzvc+NUiLncP2uuArMWLCbu9SBzvHz7e8= -github.com/VictoriaMetrics/fastcache v1.12.2 h1:N0y9ASrJ0F6h0QaC3o6uJb3NIZ9VKLjCM7NQbSmF7WI= -github.com/VictoriaMetrics/fastcache v1.12.2/go.mod h1:AmC+Nzz1+3G2eCPapF6UcsnkThDcMsQicp4xDukwJYI= +github.com/VictoriaMetrics/fastcache v1.13.0 h1:AW4mheMR5Vd9FkAPUv+NH6Nhw+fmbTMGMsNAoA/+4G0= +github.com/VictoriaMetrics/fastcache v1.13.0/go.mod h1:hHXhl4DA2fTL2HTZDJFXWgW0LNjo6B+4aj2Wmng3TjU= github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156 h1:eMwmnE/GDgah4HI848JfFxHt+iPb26b4zyfspmqY0/8= github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156/go.mod h1:Cb/ax3seSYIx7SuZdm2G2xzfwmv3TPSk2ucNfQESPXM= github.com/aws/aws-sdk-go-v2 v1.21.2 h1:+LXZ0sgo8quN9UOKXXzAWRT3FWd4NxeXWOZom9pE7GA= @@ -52,7 +52,6 @@ github.com/bits-and-blooms/bitset v1.20.0 h1:2F+rfL86jE2d/bmw7OhqUg2Sj/1rURkBn3M github.com/bits-and-blooms/bitset v1.20.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8= github.com/cespare/cp v0.1.0 h1:SE+dxFebS7Iik5LK0tsi1k9ZCxEaFX4AjQmoyA+1dJk= github.com/cespare/cp v0.1.0/go.mod h1:SOGHArjBr4JWaSDEVpWpo/hNg6RoKrls6Oh40hiwW+s= -github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/chzyer/logex v1.2.0/go.mod h1:9+9sk7u7pGNWYMkh0hdiL++6OeibzJccyQU4p4MedaY= @@ -165,8 +164,8 @@ github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= -github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb h1:PBC98N2aIaM3XXiurYmW7fx4GZkL8feAMVq7nEjURHk= -github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/golang/snappy v1.0.0 h1:Oy607GVXHs7RtbggtPBnr2RmDArIsAefDwvrdWvRhGs= +github.com/golang/snappy v1.0.0/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golangci/lint-1 v0.0.0-20181222135242-d2cdd8c08219/go.mod h1:/X8TswGSh1pIozq4ZwCfxS0WA5JGXguxk94ar/4c87Y= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= @@ -450,7 +449,6 @@ golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k= golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= From 9986270fbf78d4c3ccf03c602168f3427fc96cd4 Mon Sep 17 00:00:00 2001 From: Yuan-Yao Sung Date: Tue, 30 Sep 2025 19:30:10 +0800 Subject: [PATCH 20/61] eth/catalyst: extend payloadVersion support to osaka/post-osaka forks (#32800) This PR updates the `payloadVersion` function in `simulated_beacon.go` to handle additional following forks used during development and testing phases after Osaka. This change ensures that the simulated beacon correctly resolves the payload version for these forks, enabling consistent and valid execution payload handling during local testing or simulation. --- eth/catalyst/simulated_beacon.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/eth/catalyst/simulated_beacon.go b/eth/catalyst/simulated_beacon.go index 0642d6a1ad..c10990c233 100644 --- a/eth/catalyst/simulated_beacon.go +++ b/eth/catalyst/simulated_beacon.go @@ -100,7 +100,7 @@ type SimulatedBeacon struct { func payloadVersion(config *params.ChainConfig, time uint64) engine.PayloadVersion { switch config.LatestFork(time) { - case forks.Prague, forks.Cancun: + case forks.BPO5, forks.BPO4, forks.BPO3, forks.BPO2, forks.BPO1, forks.Osaka, forks.Prague, forks.Cancun: return engine.PayloadV3 case forks.Paris, forks.Shanghai: return engine.PayloadV2 From f9756bb8857706e64fdaf709a092ac1acc7b45e9 Mon Sep 17 00:00:00 2001 From: Zach Brown Date: Tue, 30 Sep 2025 19:30:47 +0800 Subject: [PATCH 21/61] p2p: fix error message in test (#32804) --- p2p/enode/iter_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/p2p/enode/iter_test.go b/p2p/enode/iter_test.go index 577f9c2825..922e1cde19 100644 --- a/p2p/enode/iter_test.go +++ b/p2p/enode/iter_test.go @@ -45,7 +45,7 @@ func TestReadNodesCycle(t *testing.T) { nodes := ReadNodes(iter, 10) checkNodes(t, nodes, 3) if iter.count != 10 { - t.Fatalf("%d calls to Next, want %d", iter.count, 100) + t.Fatalf("%d calls to Next, want %d", iter.count, 10) } } From bb00d26bbe6bdee45d8d0fe231a326e345d53a68 Mon Sep 17 00:00:00 2001 From: Zach Brown Date: Tue, 30 Sep 2025 19:31:42 +0800 Subject: [PATCH 22/61] signer/core: fix error message in test (#32807) --- signer/core/api_test.go | 2 +- signer/core/signed_data_test.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/signer/core/api_test.go b/signer/core/api_test.go index 0e16a1b7fd..ed4fdc5096 100644 --- a/signer/core/api_test.go +++ b/signer/core/api_test.go @@ -264,7 +264,7 @@ func TestSignTx(t *testing.T) { t.Errorf("Expected nil-response, got %v", res) } if err != keystore.ErrDecrypt { - t.Errorf("Expected ErrLocked! %v", err) + t.Errorf("Expected ErrDecrypt! %v", err) } control.approveCh <- "No way" res, err = api.SignTransaction(t.Context(), tx, &methodSig) diff --git a/signer/core/signed_data_test.go b/signer/core/signed_data_test.go index 001f6b6838..8455aaf9c5 100644 --- a/signer/core/signed_data_test.go +++ b/signer/core/signed_data_test.go @@ -202,7 +202,7 @@ func TestSignData(t *testing.T) { t.Errorf("Expected nil-data, got %x", signature) } if err != keystore.ErrDecrypt { - t.Errorf("Expected ErrLocked! '%v'", err) + t.Errorf("Expected ErrDecrypt! '%v'", err) } control.approveCh <- "No way" signature, err = api.SignData(context.Background(), apitypes.TextPlain.Mime, a, hexutil.Encode([]byte("EHLO world"))) From 1487a8577d1566497e161a04f8cee3204d4b3d36 Mon Sep 17 00:00:00 2001 From: Zach Brown Date: Tue, 30 Sep 2025 19:33:36 +0800 Subject: [PATCH 23/61] params: fix banner message (#32796) --- params/config.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/params/config.go b/params/config.go index 4e885cbdd4..e796d75535 100644 --- a/params/config.go +++ b/params/config.go @@ -624,7 +624,7 @@ func (c *ChainConfig) Description() string { } banner += fmt.Sprintf(" - Tangerine Whistle (EIP 150): #%-8v\n", c.EIP150Block) banner += fmt.Sprintf(" - Spurious Dragon/1 (EIP 155): #%-8v\n", c.EIP155Block) - banner += fmt.Sprintf(" - Spurious Dragon/2 (EIP 158): #%-8v\n", c.EIP155Block) + banner += fmt.Sprintf(" - Spurious Dragon/2 (EIP 158): #%-8v\n", c.EIP158Block) banner += fmt.Sprintf(" - Byzantium: #%-8v\n", c.ByzantiumBlock) banner += fmt.Sprintf(" - Constantinople: #%-8v\n", c.ConstantinopleBlock) banner += fmt.Sprintf(" - Petersburg: #%-8v\n", c.PetersburgBlock) From 057667151b1940e7403a97bc9fcbc207cd5b5045 Mon Sep 17 00:00:00 2001 From: Martin HS Date: Wed, 1 Oct 2025 10:05:49 +0200 Subject: [PATCH 24/61] core/types, trie: reduce allocations in derivesha (#30747) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Alternative to #30746, potential follow-up to #30743 . This PR makes the stacktrie always copy incoming value buffers, and reuse them internally. Improvement in #30743: ``` goos: linux goarch: amd64 pkg: github.com/ethereum/go-ethereum/core/types cpu: 12th Gen Intel(R) Core(TM) i7-1270P │ derivesha.1 │ derivesha.2 │ │ sec/op │ sec/op vs base │ DeriveSha200/stack_trie-8 477.8µ ± 2% 430.0µ ± 12% -10.00% (p=0.000 n=10) │ derivesha.1 │ derivesha.2 │ │ B/op │ B/op vs base │ DeriveSha200/stack_trie-8 45.17Ki ± 0% 25.65Ki ± 0% -43.21% (p=0.000 n=10) │ derivesha.1 │ derivesha.2 │ │ allocs/op │ allocs/op vs base │ DeriveSha200/stack_trie-8 1259.0 ± 0% 232.0 ± 0% -81.57% (p=0.000 n=10) ``` This PR further enhances that: ``` goos: linux goarch: amd64 pkg: github.com/ethereum/go-ethereum/core/types cpu: 12th Gen Intel(R) Core(TM) i7-1270P │ derivesha.2 │ derivesha.3 │ │ sec/op │ sec/op vs base │ DeriveSha200/stack_trie-8 430.0µ ± 12% 423.6µ ± 13% ~ (p=0.739 n=10) │ derivesha.2 │ derivesha.3 │ │ B/op │ B/op vs base │ DeriveSha200/stack_trie-8 25.654Ki ± 0% 4.960Ki ± 0% -80.67% (p=0.000 n=10) │ derivesha.2 │ derivesha.3 │ │ allocs/op │ allocs/op vs base │ DeriveSha200/stack_trie-8 232.00 ± 0% 37.00 ± 0% -84.05% (p=0.000 n=10) ``` So the total derivesha-improvement over *both PRS* is: ``` goos: linux goarch: amd64 pkg: github.com/ethereum/go-ethereum/core/types cpu: 12th Gen Intel(R) Core(TM) i7-1270P │ derivesha.1 │ derivesha.3 │ │ sec/op │ sec/op vs base │ DeriveSha200/stack_trie-8 477.8µ ± 2% 423.6µ ± 13% -11.33% (p=0.015 n=10) │ derivesha.1 │ derivesha.3 │ │ B/op │ B/op vs base │ DeriveSha200/stack_trie-8 45.171Ki ± 0% 4.960Ki ± 0% -89.02% (p=0.000 n=10) │ derivesha.1 │ derivesha.3 │ │ allocs/op │ allocs/op vs base │ DeriveSha200/stack_trie-8 1259.00 ± 0% 37.00 ± 0% -97.06% (p=0.000 n=10) ``` Since this PR always copies the incoming value, it adds a little bit of a penalty on the previous insert-benchmark, which copied nothing (always passed the same empty slice as input) : ``` goos: linux goarch: amd64 pkg: github.com/ethereum/go-ethereum/trie cpu: 12th Gen Intel(R) Core(TM) i7-1270P │ stacktrie.7 │ stacktrie.10 │ │ sec/op │ sec/op vs base │ Insert100K-8 88.21m ± 34% 92.37m ± 31% ~ (p=0.280 n=10) │ stacktrie.7 │ stacktrie.10 │ │ B/op │ B/op vs base │ Insert100K-8 3.424Ki ± 3% 4.581Ki ± 3% +33.80% (p=0.000 n=10) │ stacktrie.7 │ stacktrie.10 │ │ allocs/op │ allocs/op vs base │ Insert100K-8 22.00 ± 5% 26.00 ± 4% +18.18% (p=0.000 n=10) ``` --------- Co-authored-by: Gary Rong Co-authored-by: Felix Lange --- core/types/block.go | 2 +- core/types/hashing.go | 27 ++++++++----- core/types/hashing_test.go | 39 ++++++++++++------ internal/blocktest/test_hash.go | 5 ++- trie/bytepool.go | 51 ++++++++++++++++++++---- trie/list_hasher.go | 56 ++++++++++++++++++++++++++ trie/stacktrie.go | 70 +++++++++++++++++++-------------- trie/trie.go | 4 +- 8 files changed, 189 insertions(+), 65 deletions(-) create mode 100644 trie/list_hasher.go diff --git a/core/types/block.go b/core/types/block.go index da9614793a..b5b6468a13 100644 --- a/core/types/block.go +++ b/core/types/block.go @@ -240,7 +240,7 @@ type extblock struct { // // The receipt's bloom must already calculated for the block's bloom to be // correctly calculated. -func NewBlock(header *Header, body *Body, receipts []*Receipt, hasher TrieHasher) *Block { +func NewBlock(header *Header, body *Body, receipts []*Receipt, hasher ListHasher) *Block { if body == nil { body = &Body{} } diff --git a/core/types/hashing.go b/core/types/hashing.go index 3cc22d50d1..98fe64e15a 100644 --- a/core/types/hashing.go +++ b/core/types/hashing.go @@ -27,7 +27,7 @@ import ( "github.com/ethereum/go-ethereum/rlp" ) -// hasherPool holds LegacyKeccak256 hashers for rlpHash. +// hasherPool holds LegacyKeccak256 buffer for rlpHash. var hasherPool = sync.Pool{ New: func() interface{} { return crypto.NewKeccakState() }, } @@ -75,11 +75,17 @@ func prefixedRlpHash(prefix byte, x interface{}) (h common.Hash) { return h } -// TrieHasher is the tool used to calculate the hash of derivable list. -// This is internal, do not use. -type TrieHasher interface { +// ListHasher defines the interface for computing the hash of a derivable list. +type ListHasher interface { + // Reset clears the internal state of the hasher, preparing it for reuse. Reset() - Update([]byte, []byte) error + + // Update inserts the given key-value pair into the hasher. + // The implementation must copy the provided slices, allowing the caller + // to safely modify them after the call returns. + Update(key []byte, value []byte) error + + // Hash computes and returns the final hash of all inserted key-value pairs. Hash() common.Hash } @@ -91,19 +97,20 @@ type DerivableList interface { EncodeIndex(int, *bytes.Buffer) } +// encodeForDerive encodes the element in the list at the position i into the buffer. func encodeForDerive(list DerivableList, i int, buf *bytes.Buffer) []byte { buf.Reset() list.EncodeIndex(i, buf) - // It's really unfortunate that we need to perform this copy. - // StackTrie holds onto the values until Hash is called, so the values - // written to it must not alias. - return common.CopyBytes(buf.Bytes()) + return buf.Bytes() } // DeriveSha creates the tree hashes of transactions, receipts, and withdrawals in a block header. -func DeriveSha(list DerivableList, hasher TrieHasher) common.Hash { +func DeriveSha(list DerivableList, hasher ListHasher) common.Hash { hasher.Reset() + // Allocate a buffer for value encoding. As the hasher is claimed that all + // supplied key value pairs will be copied by hasher and safe to reuse the + // encoding buffer. valueBuf := encodeBufferPool.Get().(*bytes.Buffer) defer encodeBufferPool.Put(valueBuf) diff --git a/core/types/hashing_test.go b/core/types/hashing_test.go index 54adbc73e8..a7153bf09a 100644 --- a/core/types/hashing_test.go +++ b/core/types/hashing_test.go @@ -26,12 +26,10 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" - "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/trie" - "github.com/ethereum/go-ethereum/triedb" ) func TestDeriveSha(t *testing.T) { @@ -40,7 +38,7 @@ func TestDeriveSha(t *testing.T) { t.Fatal(err) } for len(txs) < 1000 { - exp := types.DeriveSha(txs, trie.NewEmpty(triedb.NewDatabase(rawdb.NewMemoryDatabase(), nil))) + exp := types.DeriveSha(txs, trie.NewListHasher()) got := types.DeriveSha(txs, trie.NewStackTrie(nil)) if !bytes.Equal(got[:], exp[:]) { t.Fatalf("%d txs: got %x exp %x", len(txs), got, exp) @@ -76,30 +74,45 @@ func TestEIP2718DeriveSha(t *testing.T) { } } +// goos: darwin +// goarch: arm64 +// pkg: github.com/ethereum/go-ethereum/core/types +// cpu: Apple M1 Pro +// BenchmarkDeriveSha200 +// BenchmarkDeriveSha200/std_trie +// BenchmarkDeriveSha200/std_trie-8 6754 174074 ns/op 80054 B/op 1926 allocs/op +// BenchmarkDeriveSha200/stack_trie +// BenchmarkDeriveSha200/stack_trie-8 7296 162675 ns/op 745 B/op 19 allocs/op func BenchmarkDeriveSha200(b *testing.B) { txs, err := genTxs(200) if err != nil { b.Fatal(err) } - var exp common.Hash - var got common.Hash + want := types.DeriveSha(txs, trie.NewListHasher()) + b.Run("std_trie", func(b *testing.B) { b.ReportAllocs() + var have common.Hash for b.Loop() { - exp = types.DeriveSha(txs, trie.NewEmpty(triedb.NewDatabase(rawdb.NewMemoryDatabase(), nil))) + have = types.DeriveSha(txs, trie.NewListHasher()) + } + if have != want { + b.Errorf("have %x want %x", have, want) } }) + st := trie.NewStackTrie(nil) b.Run("stack_trie", func(b *testing.B) { - b.ResetTimer() b.ReportAllocs() + var have common.Hash for b.Loop() { - got = types.DeriveSha(txs, trie.NewStackTrie(nil)) + st.Reset() + have = types.DeriveSha(txs, st) + } + if have != want { + b.Errorf("have %x want %x", have, want) } }) - if got != exp { - b.Errorf("got %x exp %x", got, exp) - } } func TestFuzzDeriveSha(t *testing.T) { @@ -107,7 +120,7 @@ func TestFuzzDeriveSha(t *testing.T) { rndSeed := mrand.Int() for i := 0; i < 10; i++ { seed := rndSeed + i - exp := types.DeriveSha(newDummy(i), trie.NewEmpty(triedb.NewDatabase(rawdb.NewMemoryDatabase(), nil))) + exp := types.DeriveSha(newDummy(i), trie.NewListHasher()) got := types.DeriveSha(newDummy(i), trie.NewStackTrie(nil)) if !bytes.Equal(got[:], exp[:]) { printList(t, newDummy(seed)) @@ -135,7 +148,7 @@ func TestDerivableList(t *testing.T) { }, } for i, tc := range tcs[1:] { - exp := types.DeriveSha(flatList(tc), trie.NewEmpty(triedb.NewDatabase(rawdb.NewMemoryDatabase(), nil))) + exp := types.DeriveSha(flatList(tc), trie.NewListHasher()) got := types.DeriveSha(flatList(tc), trie.NewStackTrie(nil)) if !bytes.Equal(got[:], exp[:]) { t.Fatalf("case %d: got %x exp %x", i, got, exp) diff --git a/internal/blocktest/test_hash.go b/internal/blocktest/test_hash.go index 4d2b077e89..b3e7098e2b 100644 --- a/internal/blocktest/test_hash.go +++ b/internal/blocktest/test_hash.go @@ -23,6 +23,7 @@ package blocktest import ( + "bytes" "hash" "github.com/ethereum/go-ethereum/common" @@ -48,8 +49,8 @@ func (h *testHasher) Reset() { // Update updates the hash state with the given key and value. func (h *testHasher) Update(key, val []byte) error { - h.hasher.Write(key) - h.hasher.Write(val) + h.hasher.Write(bytes.Clone(key)) + h.hasher.Write(bytes.Clone(val)) return nil } diff --git a/trie/bytepool.go b/trie/bytepool.go index 4f9c5672fd..31be7ae749 100644 --- a/trie/bytepool.go +++ b/trie/bytepool.go @@ -32,8 +32,8 @@ func newBytesPool(sliceCap, nitems int) *bytesPool { } } -// Get returns a slice. Safe for concurrent use. -func (bp *bytesPool) Get() []byte { +// get returns a slice. Safe for concurrent use. +func (bp *bytesPool) get() []byte { select { case b := <-bp.c: return b @@ -42,18 +42,18 @@ func (bp *bytesPool) Get() []byte { } } -// GetWithSize returns a slice with specified byte slice size. -func (bp *bytesPool) GetWithSize(s int) []byte { - b := bp.Get() +// getWithSize returns a slice with specified byte slice size. +func (bp *bytesPool) getWithSize(s int) []byte { + b := bp.get() if cap(b) < s { return make([]byte, s) } return b[:s] } -// Put returns a slice to the pool. Safe for concurrent use. This method +// put returns a slice to the pool. Safe for concurrent use. This method // will ignore slices that are too small or too large (>3x the cap) -func (bp *bytesPool) Put(b []byte) { +func (bp *bytesPool) put(b []byte) { if c := cap(b); c < bp.w || c > 3*bp.w { return } @@ -62,3 +62,40 @@ func (bp *bytesPool) Put(b []byte) { default: } } + +// unsafeBytesPool is a pool for byte slices. It is not safe for concurrent use. +type unsafeBytesPool struct { + items [][]byte + w int +} + +// newUnsafeBytesPool creates a new unsafeBytesPool. The sliceCap sets the +// capacity of newly allocated slices, and the nitems determines how many +// items the pool will hold, at maximum. +func newUnsafeBytesPool(sliceCap, nitems int) *unsafeBytesPool { + return &unsafeBytesPool{ + items: make([][]byte, 0, nitems), + w: sliceCap, + } +} + +// Get returns a slice with pre-allocated space. +func (bp *unsafeBytesPool) get() []byte { + if len(bp.items) > 0 { + last := bp.items[len(bp.items)-1] + bp.items = bp.items[:len(bp.items)-1] + return last + } + return make([]byte, 0, bp.w) +} + +// put returns a slice to the pool. This method will ignore slices that are +// too small or too large (>3x the cap) +func (bp *unsafeBytesPool) put(b []byte) { + if c := cap(b); c < bp.w || c > 3*bp.w { + return + } + if len(bp.items) < cap(bp.items) { + bp.items = append(bp.items, b) + } +} diff --git a/trie/list_hasher.go b/trie/list_hasher.go new file mode 100644 index 0000000000..8f334f9901 --- /dev/null +++ b/trie/list_hasher.go @@ -0,0 +1,56 @@ +// 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 trie + +import ( + "bytes" + + "github.com/ethereum/go-ethereum/common" +) + +// ListHasher is a wrapper of the Merkle-Patricia-Trie, which implements +// types.ListHasher. Compared to a Trie instance, the Update method of this +// type always deep-copies its input slices. +// +// This implementation is very inefficient in terms of memory allocation, +// compared with StackTrie. It exists only for correctness comparison purposes. +type ListHasher struct { + tr *Trie +} + +// NewListHasher initializes the list hasher. +func NewListHasher() *ListHasher { + return &ListHasher{ + tr: NewEmpty(nil), + } +} + +// Reset clears the internal state prepares the ListHasher for reuse. +func (h *ListHasher) Reset() { + h.tr.reset() +} + +// Update inserts a key-value pair into the trie. +func (h *ListHasher) Update(key []byte, value []byte) error { + key, value = bytes.Clone(key), bytes.Clone(value) + return h.tr.Update(key, value) +} + +// Hash computes the root hash of all inserted key-value pairs. +func (h *ListHasher) Hash() common.Hash { + return h.tr.Hash() +} diff --git a/trie/stacktrie.go b/trie/stacktrie.go index 2b7366c3c5..18fe1eea78 100644 --- a/trie/stacktrie.go +++ b/trie/stacktrie.go @@ -28,7 +28,7 @@ import ( var ( stPool = sync.Pool{New: func() any { return new(stNode) }} bPool = newBytesPool(32, 100) - _ = types.TrieHasher((*StackTrie)(nil)) + _ = types.ListHasher((*StackTrie)(nil)) ) // OnTrieNode is a callback method invoked when a trie node is committed @@ -50,6 +50,7 @@ type StackTrie struct { onTrieNode OnTrieNode kBuf []byte // buf space used for hex-key during insertions pBuf []byte // buf space used for path during insertions + vPool *unsafeBytesPool } // NewStackTrie allocates and initializes an empty trie. The committed nodes @@ -61,6 +62,7 @@ func NewStackTrie(onTrieNode OnTrieNode) *StackTrie { onTrieNode: onTrieNode, kBuf: make([]byte, 64), pBuf: make([]byte, 64), + vPool: newUnsafeBytesPool(300, 20), } } @@ -74,6 +76,9 @@ func (t *StackTrie) grow(key []byte) { } // Update inserts a (key, value) pair into the stack trie. +// +// Note the supplied key value pair is copied and managed internally, +// they are safe to be modified after this method returns. func (t *StackTrie) Update(key, value []byte) error { if len(value) == 0 { return errors.New("trying to insert empty (deletion)") @@ -88,7 +93,14 @@ func (t *StackTrie) Update(key, value []byte) error { } else { t.last = append(t.last[:0], k...) // reuse key slice } - t.insert(t.root, k, value, t.pBuf[:0]) + vBuf := t.vPool.get() + if cap(vBuf) < len(value) { + vBuf = common.CopyBytes(value) + } else { + vBuf = vBuf[:len(value)] + copy(vBuf, value) + } + t.insert(t.root, k, vBuf, t.pBuf[:0]) return nil } @@ -108,14 +120,16 @@ func (t *StackTrie) TrieKey(key []byte) []byte { // stNode represents a node within a StackTrie type stNode struct { typ uint8 // node type (as in branch, ext, leaf) - key []byte // key chunk covered by this (leaf|ext) node - val []byte // value contained by this node if it's a leaf - children [16]*stNode // list of children (for branch and exts) + key []byte // exclusive owned key chunk covered by this (leaf|ext) node + val []byte // exclusive owned value contained by this node (leaf: value; hash: hash) + children [16]*stNode // list of children (for branch and ext) } -// newLeaf constructs a leaf node with provided node key and value. The key -// will be deep-copied in the function and safe to modify afterwards, but -// value is not. +// newLeaf constructs a leaf node with provided node key and value. +// +// The key is deep-copied within the function, so it can be safely modified +// afterwards. The value is retained directly without copying, as it is +// exclusively owned by the stackTrie. func newLeaf(key, val []byte) *stNode { st := stPool.Get().(*stNode) st.typ = leafNode @@ -146,9 +160,9 @@ const ( func (n *stNode) reset() *stNode { if n.typ == hashedNode { // On hashnodes, we 'own' the val: it is guaranteed to be not held - // by external caller. Hence, when we arrive here, we can put it back - // into the pool - bPool.Put(n.val) + // by external caller. Hence, when we arrive here, we can put it + // back into the pool + bPool.put(n.val) } n.key = n.key[:0] n.val = nil @@ -172,11 +186,6 @@ func (n *stNode) getDiffIndex(key []byte) int { } // Helper function to that inserts a (key, value) pair into the trie. -// -// - The key is not retained by this method, but always copied if needed. -// - The value is retained by this method, as long as the leaf that it represents -// remains unhashed. However: it is never modified. -// - The path is not retained by this method. func (t *StackTrie) insert(st *stNode, key, value []byte, path []byte) { switch st.typ { case branchNode: /* Branch */ @@ -235,16 +244,14 @@ func (t *StackTrie) insert(st *stNode, key, value []byte, path []byte) { } var p *stNode if diffidx == 0 { - // the break is on the first byte, so - // the current node is converted into - // a branch node. + // the break is on the first byte, so the current node + // is converted into a branch node. st.children[0] = nil - p = st st.typ = branchNode + p = st } else { - // the common prefix is at least one byte - // long, insert a new intermediate branch - // node. + // the common prefix is at least one byte long, insert + // a new intermediate branch node. st.children[0] = stPool.Get().(*stNode) st.children[0].typ = branchNode p = st.children[0] @@ -280,8 +287,8 @@ func (t *StackTrie) insert(st *stNode, key, value []byte, path []byte) { if diffidx == 0 { // Convert current leaf into a branch st.typ = branchNode - p = st st.children[0] = nil + p = st } else { // Convert current node into an ext, // and insert a child branch node. @@ -307,9 +314,7 @@ func (t *StackTrie) insert(st *stNode, key, value []byte, path []byte) { st.val = nil case emptyNode: /* Empty */ - st.typ = leafNode - st.key = append(st.key, key...) // deep-copy the key as it's volatile - st.val = value + *st = *newLeaf(key, value) case hashedNode: panic("trying to insert into hash") @@ -393,18 +398,23 @@ func (t *StackTrie) hash(st *stNode, path []byte) { st.typ = hashedNode st.key = st.key[:0] - st.val = nil // Release reference to potentially externally held slice. + // Release reference to value slice which is exclusively owned + // by stackTrie itself. + if cap(st.val) > 0 && t.vPool != nil { + t.vPool.put(st.val) + } + st.val = nil // Skip committing the non-root node if the size is smaller than 32 bytes // as tiny nodes are always embedded in their parent except root node. if len(blob) < 32 && len(path) > 0 { - st.val = bPool.GetWithSize(len(blob)) + st.val = bPool.getWithSize(len(blob)) copy(st.val, blob) return } // Write the hash to the 'val'. We allocate a new val here to not mutate // input values. - st.val = bPool.GetWithSize(32) + st.val = bPool.getWithSize(32) t.h.hashDataTo(st.val, blob) // Invoke the callback it's provided. Notably, the path and blob slices are diff --git a/trie/trie.go b/trie/trie.go index 36cc732ee8..1ef2c2f1a6 100644 --- a/trie/trie.go +++ b/trie/trie.go @@ -784,8 +784,8 @@ func (t *Trie) Witness() map[string][]byte { return t.prevalueTracer.Values() } -// Reset drops the referenced root node and cleans all internal state. -func (t *Trie) Reset() { +// reset drops the referenced root node and cleans all internal state. +func (t *Trie) reset() { t.root = nil t.owner = common.Hash{} t.unhashed = 0 From f0dc47aae393abe0bb7b084345daf9108ad8897a Mon Sep 17 00:00:00 2001 From: zzzckck <152148891+zzzckck@users.noreply.github.com> Date: Thu, 2 Oct 2025 18:43:31 +0800 Subject: [PATCH 25/61] p2p/enode: fix discovery AyncFilter deadlock on shutdown (#32572) Description: We found a occasionally node hang issue on BSC, I think Geth may also have the issue, so pick the fix patch here. The fix on BSC repo: https://github.com/bnb-chain/bsc/pull/3347 When the hang occurs, there are two routines stuck. - routine 1: AsyncFilter(...) On node start, it will run part of the DiscoveryV4 protocol, which could take considerable time, here is its hang callstack: ``` goroutine 9711 [chan receive]: // this routine was stuck on read channel: `<-f.slots` github.com/ethereum/go-ethereum/p2p/enode.AsyncFilter.func1() github.com/ethereum/go-ethereum/p2p/enode/iter.go:206 +0x125 created by github.com/ethereum/go-ethereum/p2p/enode.AsyncFilter in goroutine 1 github.com/ethereum/go-ethereum/p2p/enode/iter.go:192 +0x205 ``` - Routine 2: Node Stop It is the main routine to shutdown the process, but it got stuck when it tries to shutdown the discovery components, as it tries to drain the channel of `<-f.slots`, but the extra 1 slot will never have chance to be resumed. ``` goroutine 11796 [chan receive]: github.com/ethereum/go-ethereum/p2p/enode.(*asyncFilterIter).Close.func1() github.com/ethereum/go-ethereum/p2p/enode/iter.go:248 +0x5c sync.(*Once).doSlow(0xc032a97cb8?, 0xc032a97d18?) sync/once.go:78 +0xab sync.(*Once).Do(...) sync/once.go:69 github.com/ethereum/go-ethereum/p2p/enode.(*asyncFilterIter).Close(0xc092ff8d00?) github.com/ethereum/go-ethereum/p2p/enode/iter.go:244 +0x36 github.com/ethereum/go-ethereum/p2p/enode.(*bufferIter).Close.func1() github.com/ethereum/go-ethereum/p2p/enode/iter.go:299 +0x24 sync.(*Once).doSlow(0x11a175f?, 0x2bfe63e?) sync/once.go:78 +0xab sync.(*Once).Do(...) sync/once.go:69 github.com/ethereum/go-ethereum/p2p/enode.(*bufferIter).Close(0x30?) github.com/ethereum/go-ethereum/p2p/enode/iter.go:298 +0x36 github.com/ethereum/go-ethereum/p2p/enode.(*FairMix).Close(0xc0004bfea0) github.com/ethereum/go-ethereum/p2p/enode/iter.go:379 +0xb7 github.com/ethereum/go-ethereum/eth.(*Ethereum).Stop(0xc000997b00) github.com/ethereum/go-ethereum/eth/backend.go:960 +0x4a github.com/ethereum/go-ethereum/node.(*Node).stopServices(0xc0001362a0, {0xc012e16330, 0x1, 0xc000111410?}) github.com/ethereum/go-ethereum/node/node.go:333 +0xb3 github.com/ethereum/go-ethereum/node.(*Node).Close(0xc0001362a0) github.com/ethereum/go-ethereum/node/node.go:263 +0x167 created by github.com/ethereum/go-ethereum/cmd/utils.StartNode.func1.1 in goroutine 9729 github.com/ethereum/go-ethereum/cmd/utils/cmd.go:101 +0x78 ``` The rootcause of the hang is caused by the extra 1 slot, which was designed to make sure the routines in `AsyncFilter(...)` can be finished. This PR fixes it by making sure the extra 1 shot can always be resumed when node shutdown. --- p2p/enode/iter.go | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/p2p/enode/iter.go b/p2p/enode/iter.go index 4890321f49..265d8648de 100644 --- a/p2p/enode/iter.go +++ b/p2p/enode/iter.go @@ -178,7 +178,7 @@ type AsyncFilterFunc func(context.Context, *Node) *Node func AsyncFilter(it Iterator, check AsyncFilterFunc, workers int) Iterator { f := &asyncFilterIter{ it: ensureSourceIter(it), - slots: make(chan struct{}, workers+1), + slots: make(chan struct{}, workers+1), // extra 1 slot to make sure all the goroutines can be completed passed: make(chan iteratorItem), } for range cap(f.slots) { @@ -193,6 +193,9 @@ func AsyncFilter(it Iterator, check AsyncFilterFunc, workers int) Iterator { return case <-f.slots: } + defer func() { + f.slots <- struct{}{} // the iterator has ended + }() // read from the iterator and start checking nodes in parallel // when a node is checked, it will be sent to the passed channel // and the slot will be released @@ -201,7 +204,11 @@ func AsyncFilter(it Iterator, check AsyncFilterFunc, workers int) Iterator { nodeSource := f.it.NodeSource() // check the node async, in a separate goroutine - <-f.slots + select { + case <-ctx.Done(): + return + case <-f.slots: + } go func() { if nn := check(ctx, node); nn != nil { item := iteratorItem{nn, nodeSource} @@ -213,8 +220,6 @@ func AsyncFilter(it Iterator, check AsyncFilterFunc, workers int) Iterator { f.slots <- struct{}{} }() } - // the iterator has ended - f.slots <- struct{}{} }() return f From fc8c8c1314a0fafc56297332729c2c00372e837e Mon Sep 17 00:00:00 2001 From: hero5512 Date: Thu, 2 Oct 2025 08:34:06 -0400 Subject: [PATCH 26/61] core: refactor StateProcessor to accept ChainContext interface (#32739) This pr implements https://github.com/ethereum/go-ethereum/issues/32733 to make StateProcessor more customisable. ## Compatibility notes This introduces a breaking change to users using geth EVM as a library. The `NewStateProcessor` function now takes one parameter which has the chainConfig embedded instead of 2 parameters. --- core/blockchain.go | 2 +- core/evm.go | 9 ++------- core/state_processor.go | 28 ++++++++++++++++------------ core/stateless.go | 2 +- core/vm/runtime/runtime_test.go | 12 ++++++++++++ eth/tracers/api.go | 1 + eth/tracers/api_test.go | 4 ++++ internal/ethapi/api.go | 16 ++++++++++++++++ internal/ethapi/simulate.go | 20 ++++++++++++++++++++ tests/state_test_util.go | 3 +++ 10 files changed, 76 insertions(+), 21 deletions(-) diff --git a/core/blockchain.go b/core/blockchain.go index 30f3da3004..71eb4c45a2 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -394,7 +394,7 @@ func NewBlockChain(db ethdb.Database, genesis *Genesis, engine consensus.Engine, bc.statedb = state.NewDatabase(bc.triedb, nil) bc.validator = NewBlockValidator(chainConfig, bc) bc.prefetcher = newStatePrefetcher(chainConfig, bc.hc) - bc.processor = NewStateProcessor(chainConfig, bc.hc) + bc.processor = NewStateProcessor(bc.hc) genesisHeader := bc.GetHeaderByNumber(0) if genesisHeader == nil { diff --git a/core/evm.go b/core/evm.go index 41b4e6ac58..18d940fdd2 100644 --- a/core/evm.go +++ b/core/evm.go @@ -25,21 +25,16 @@ import ( "github.com/ethereum/go-ethereum/core/tracing" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/vm" - "github.com/ethereum/go-ethereum/params" "github.com/holiman/uint256" ) // ChainContext supports retrieving headers and consensus parameters from the // current blockchain to be used during transaction processing. type ChainContext interface { + consensus.ChainHeaderReader + // Engine retrieves the chain's consensus engine. Engine() consensus.Engine - - // GetHeader returns the header corresponding to the hash/number argument pair. - GetHeader(common.Hash, uint64) *types.Header - - // Config returns the chain's configuration. - Config() *params.ChainConfig } // NewEVMBlockContext creates a new context for use in the EVM. diff --git a/core/state_processor.go b/core/state_processor.go index 4a5e69ca6e..b66046f501 100644 --- a/core/state_processor.go +++ b/core/state_processor.go @@ -35,18 +35,21 @@ import ( // // StateProcessor implements Processor. type StateProcessor struct { - config *params.ChainConfig // Chain configuration options - chain *HeaderChain // Canonical header chain + chain ChainContext // Chain context interface } // NewStateProcessor initialises a new StateProcessor. -func NewStateProcessor(config *params.ChainConfig, chain *HeaderChain) *StateProcessor { +func NewStateProcessor(chain ChainContext) *StateProcessor { return &StateProcessor{ - config: config, - chain: chain, + chain: chain, } } +// chainConfig returns the chain configuration. +func (p *StateProcessor) chainConfig() *params.ChainConfig { + return p.chain.Config() +} + // Process processes the state changes according to the Ethereum rules by running // the transaction messages using the statedb and applying any rewards to both // the processor (coinbase) and any included uncles. @@ -56,6 +59,7 @@ func NewStateProcessor(config *params.ChainConfig, chain *HeaderChain) *StatePro // transactions failed to execute due to insufficient gas it will return an error. func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg vm.Config) (*ProcessResult, error) { var ( + config = p.chainConfig() receipts types.Receipts usedGas = new(uint64) header = block.Header() @@ -66,12 +70,12 @@ func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg ) // Mutate the block and state according to any hard-fork specs - if p.config.DAOForkSupport && p.config.DAOForkBlock != nil && p.config.DAOForkBlock.Cmp(block.Number()) == 0 { + if config.DAOForkSupport && config.DAOForkBlock != nil && config.DAOForkBlock.Cmp(block.Number()) == 0 { misc.ApplyDAOHardFork(statedb) } var ( context vm.BlockContext - signer = types.MakeSigner(p.config, header.Number, header.Time) + signer = types.MakeSigner(config, header.Number, header.Time) ) // Apply pre-execution system calls. @@ -80,12 +84,12 @@ func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg tracingStateDB = state.NewHookedState(statedb, hooks) } context = NewEVMBlockContext(header, p.chain, nil) - evm := vm.NewEVM(context, tracingStateDB, p.config, cfg) + evm := vm.NewEVM(context, tracingStateDB, config, cfg) if beaconRoot := block.BeaconRoot(); beaconRoot != nil { ProcessBeaconBlockRoot(*beaconRoot, evm) } - if p.config.IsPrague(block.Number(), block.Time()) || p.config.IsVerkle(block.Number(), block.Time()) { + if config.IsPrague(block.Number(), block.Time()) || config.IsVerkle(block.Number(), block.Time()) { ProcessParentBlockHash(block.ParentHash(), evm) } @@ -106,10 +110,10 @@ func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg } // Read requests if Prague is enabled. var requests [][]byte - if p.config.IsPrague(block.Number(), block.Time()) { + if config.IsPrague(block.Number(), block.Time()) { requests = [][]byte{} // EIP-6110 - if err := ParseDepositLogs(&requests, allLogs, p.config); err != nil { + if err := ParseDepositLogs(&requests, allLogs, config); err != nil { return nil, fmt.Errorf("failed to parse deposit logs: %w", err) } // EIP-7002 @@ -123,7 +127,7 @@ func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg } // Finalize the block, applying any consensus engine specific extras (e.g. block rewards) - p.chain.engine.Finalize(p.chain, header, tracingStateDB, block.Body()) + p.chain.Engine().Finalize(p.chain, header, tracingStateDB, block.Body()) return &ProcessResult{ Receipts: receipts, diff --git a/core/stateless.go b/core/stateless.go index d21a62b4a5..b20c909da6 100644 --- a/core/stateless.go +++ b/core/stateless.go @@ -62,7 +62,7 @@ func ExecuteStateless(config *params.ChainConfig, vmconfig vm.Config, block *typ headerCache: lru.NewCache[common.Hash, *types.Header](256), engine: beacon.New(ethash.NewFaker()), } - processor := NewStateProcessor(config, chain) + processor := NewStateProcessor(chain) validator := NewBlockValidator(config, nil) // No chain, we only validate the state, not the block // Run the stateless blocks processing and self-validate certain fields diff --git a/core/vm/runtime/runtime_test.go b/core/vm/runtime/runtime_test.go index ddd32df039..a001d81623 100644 --- a/core/vm/runtime/runtime_test.go +++ b/core/vm/runtime/runtime_test.go @@ -312,6 +312,18 @@ func (d *dummyChain) Config() *params.ChainConfig { return nil } +func (d *dummyChain) CurrentHeader() *types.Header { + return nil +} + +func (d *dummyChain) GetHeaderByNumber(n uint64) *types.Header { + return d.GetHeader(common.Hash{}, n) +} + +func (d *dummyChain) GetHeaderByHash(h common.Hash) *types.Header { + return nil +} + // TestBlockhash tests the blockhash operation. It's a bit special, since it internally // requires access to a chain reader. func TestBlockhash(t *testing.T) { diff --git a/eth/tracers/api.go b/eth/tracers/api.go index a05b7a7a4a..aebeb48463 100644 --- a/eth/tracers/api.go +++ b/eth/tracers/api.go @@ -80,6 +80,7 @@ type StateReleaseFunc func() type Backend interface { HeaderByHash(ctx context.Context, hash common.Hash) (*types.Header, error) HeaderByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Header, error) + CurrentHeader() *types.Header BlockByHash(ctx context.Context, hash common.Hash) (*types.Block, error) BlockByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Block, error) GetCanonicalTransaction(txHash common.Hash) (bool, *types.Transaction, common.Hash, uint64, uint64) diff --git a/eth/tracers/api_test.go b/eth/tracers/api_test.go index 39c39ff05d..4173d2a791 100644 --- a/eth/tracers/api_test.go +++ b/eth/tracers/api_test.go @@ -142,6 +142,10 @@ func (b *testBackend) ChainDb() ethdb.Database { return b.chaindb } +func (b *testBackend) CurrentHeader() *types.Header { + return b.chain.CurrentHeader() +} + // teardown releases the associated resources. func (b *testBackend) teardown() { b.chain.Stop() diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index 2432bb70b8..c3f267027c 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -636,6 +636,8 @@ func (api *BlockChainAPI) GetBlockReceipts(ctx context.Context, blockNrOrHash rp type ChainContextBackend interface { Engine() consensus.Engine HeaderByNumber(context.Context, rpc.BlockNumber) (*types.Header, error) + HeaderByHash(context.Context, common.Hash) (*types.Header, error) + CurrentHeader() *types.Header ChainConfig() *params.ChainConfig } @@ -669,6 +671,20 @@ func (context *ChainContext) Config() *params.ChainConfig { return context.b.ChainConfig() } +func (context *ChainContext) CurrentHeader() *types.Header { + return context.b.CurrentHeader() +} + +func (context *ChainContext) GetHeaderByNumber(number uint64) *types.Header { + header, _ := context.b.HeaderByNumber(context.ctx, rpc.BlockNumber(number)) + return header +} + +func (context *ChainContext) GetHeaderByHash(hash common.Hash) *types.Header { + header, _ := context.b.HeaderByHash(context.ctx, hash) + return header +} + func doCall(ctx context.Context, b Backend, args TransactionArgs, state *state.StateDB, header *types.Header, overrides *override.StateOverride, blockOverrides *override.BlockOverrides, timeout time.Duration, globalGasCap uint64) (*core.ExecutionResult, error) { blockCtx := core.NewEVMBlockContext(header, NewChainContext(ctx, b), nil) if blockOverrides != nil { diff --git a/internal/ethapi/simulate.go b/internal/ethapi/simulate.go index 75b5c5ffa8..2bda69b315 100644 --- a/internal/ethapi/simulate.go +++ b/internal/ethapi/simulate.go @@ -541,3 +541,23 @@ func (b *simBackend) HeaderByNumber(ctx context.Context, number rpc.BlockNumber) func (b *simBackend) ChainConfig() *params.ChainConfig { return b.b.ChainConfig() } + +func (b *simBackend) HeaderByHash(ctx context.Context, hash common.Hash) (*types.Header, error) { + if b.base.Hash() == hash { + return b.base, nil + } + if header, err := b.b.HeaderByHash(ctx, hash); err == nil { + return header, nil + } + // Check simulated headers + for _, header := range b.headers { + if header.Hash() == hash { + return header, nil + } + } + return nil, errors.New("header not found") +} + +func (b *simBackend) CurrentHeader() *types.Header { + return b.b.CurrentHeader() +} diff --git a/tests/state_test_util.go b/tests/state_test_util.go index b8d3c4fb92..1d6cc8db70 100644 --- a/tests/state_test_util.go +++ b/tests/state_test_util.go @@ -559,3 +559,6 @@ type dummyChain struct { func (d *dummyChain) Engine() consensus.Engine { return nil } func (d *dummyChain) GetHeader(h common.Hash, n uint64) *types.Header { return nil } func (d *dummyChain) Config() *params.ChainConfig { return d.config } +func (d *dummyChain) CurrentHeader() *types.Header { return nil } +func (d *dummyChain) GetHeaderByNumber(n uint64) *types.Header { return nil } +func (d *dummyChain) GetHeaderByHash(h common.Hash) *types.Header { return nil } From 4927e89647a0d27f284472aa563890035d3662db Mon Sep 17 00:00:00 2001 From: Csaba Kiraly Date: Thu, 2 Oct 2025 17:27:35 +0200 Subject: [PATCH 27/61] p2p/enode: fix asyncfilter comment (#32823) just finisher the sentence Signed-off-by: Csaba Kiraly --- p2p/enode/iter.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/p2p/enode/iter.go b/p2p/enode/iter.go index 265d8648de..54c2fc7258 100644 --- a/p2p/enode/iter.go +++ b/p2p/enode/iter.go @@ -174,7 +174,8 @@ type AsyncFilterFunc func(context.Context, *Node) *Node // AsyncFilter creates an iterator which checks nodes in parallel. // The 'check' function is called on multiple goroutines to filter each node // from the upstream iterator. When check returns nil, the node will be skipped. -// It can also return a new node to be returned by the iterator instead of the . +// It can also return a new node to be returned by the iterator instead of the +// original one. func AsyncFilter(it Iterator, check AsyncFilterFunc, workers int) Iterator { f := &asyncFilterIter{ it: ensureSourceIter(it), From 1e4b39ed122f475ac3f776ae66c8d065e845a84e Mon Sep 17 00:00:00 2001 From: hero5512 Date: Thu, 2 Oct 2025 11:32:20 -0400 Subject: [PATCH 28/61] trie: cleaner array concatenation (#32756) It uses the slices.Concat and slices.Clone methods available now in Go. --- trie/sync.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/trie/sync.go b/trie/sync.go index 8d0ce6901c..404d67f154 100644 --- a/trie/sync.go +++ b/trie/sync.go @@ -19,6 +19,7 @@ package trie import ( "errors" "fmt" + "slices" "sync" "github.com/ethereum/go-ethereum/common" @@ -553,7 +554,7 @@ func (s *Sync) children(req *nodeRequest, object node) ([]*nodeRequest, error) { } children = []childNode{{ node: node.Val, - path: append(append([]byte(nil), req.path...), key...), + path: slices.Concat(req.path, key), }} // Mark all internal nodes between shortNode and its **in disk** // child as invalid. This is essential in the case of path mode @@ -595,7 +596,7 @@ func (s *Sync) children(req *nodeRequest, object node) ([]*nodeRequest, error) { if node.Children[i] != nil { children = append(children, childNode{ node: node.Children[i], - path: append(append([]byte(nil), req.path...), byte(i)), + path: append(slices.Clone(req.path), byte(i)), }) } } From 477ee5873ba9fde828c7964fcb9f881799c9d6c2 Mon Sep 17 00:00:00 2001 From: Nikita Mescheryakov Date: Mon, 6 Oct 2025 21:19:25 +0500 Subject: [PATCH 29/61] internal/ethapi: add timestamp to logs in eth_simulate (#32831) Adds blockTimestamp to the logs in response of eth_simulateV1. --------- Co-authored-by: Sina Mahmoodi --- internal/ethapi/api_test.go | 30 +++++++++++++++++------------- internal/ethapi/logtracer.go | 21 ++++++++++++--------- internal/ethapi/simulate.go | 2 +- 3 files changed, 30 insertions(+), 23 deletions(-) diff --git a/internal/ethapi/api_test.go b/internal/ethapi/api_test.go index 2e0b1c3bc0..d3278c04e7 100644 --- a/internal/ethapi/api_test.go +++ b/internal/ethapi/api_test.go @@ -1327,10 +1327,11 @@ func TestSimulateV1(t *testing.T) { validation = true ) type log struct { - Address common.Address `json:"address"` - Topics []common.Hash `json:"topics"` - Data hexutil.Bytes `json:"data"` - BlockNumber hexutil.Uint64 `json:"blockNumber"` + Address common.Address `json:"address"` + Topics []common.Hash `json:"topics"` + Data hexutil.Bytes `json:"data"` + BlockNumber hexutil.Uint64 `json:"blockNumber"` + BlockTimestamp hexutil.Uint64 `json:"blockTimestamp"` // Skip txHash //TxHash common.Hash `json:"transactionHash" gencodec:"required"` TxIndex hexutil.Uint `json:"transactionIndex"` @@ -1677,10 +1678,11 @@ func TestSimulateV1(t *testing.T) { Calls: []callRes{{ ReturnValue: "0x", Logs: []log{{ - Address: randomAccounts[2].addr, - Topics: []common.Hash{common.HexToHash("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")}, - BlockNumber: hexutil.Uint64(11), - Data: hexutil.Bytes{}, + Address: randomAccounts[2].addr, + Topics: []common.Hash{common.HexToHash("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")}, + BlockNumber: hexutil.Uint64(11), + BlockTimestamp: hexutil.Uint64(0x70), + Data: hexutil.Bytes{}, }}, GasUsed: "0x5508", Status: "0x1", @@ -1853,8 +1855,9 @@ func TestSimulateV1(t *testing.T) { addressToHash(accounts[0].addr), addressToHash(randomAccounts[0].addr), }, - Data: hexutil.Bytes(common.BigToHash(big.NewInt(50)).Bytes()), - BlockNumber: hexutil.Uint64(11), + Data: hexutil.Bytes(common.BigToHash(big.NewInt(50)).Bytes()), + BlockNumber: hexutil.Uint64(11), + BlockTimestamp: hexutil.Uint64(0x70), }, { Address: transferAddress, Topics: []common.Hash{ @@ -1862,9 +1865,10 @@ func TestSimulateV1(t *testing.T) { addressToHash(randomAccounts[0].addr), addressToHash(fixedAccount.addr), }, - Data: hexutil.Bytes(common.BigToHash(big.NewInt(100)).Bytes()), - BlockNumber: hexutil.Uint64(11), - Index: hexutil.Uint(1), + Data: hexutil.Bytes(common.BigToHash(big.NewInt(100)).Bytes()), + BlockNumber: hexutil.Uint64(11), + BlockTimestamp: hexutil.Uint64(0x70), + Index: hexutil.Uint(1), }}, Status: "0x1", }}, diff --git a/internal/ethapi/logtracer.go b/internal/ethapi/logtracer.go index 456aa93736..54d2d653ea 100644 --- a/internal/ethapi/logtracer.go +++ b/internal/ethapi/logtracer.go @@ -53,15 +53,17 @@ type tracer struct { count int traceTransfers bool blockNumber uint64 + blockTimestamp uint64 blockHash common.Hash txHash common.Hash txIdx uint } -func newTracer(traceTransfers bool, blockNumber uint64, blockHash, txHash common.Hash, txIndex uint) *tracer { +func newTracer(traceTransfers bool, blockNumber uint64, blockTimestamp uint64, blockHash, txHash common.Hash, txIndex uint) *tracer { return &tracer{ traceTransfers: traceTransfers, blockNumber: blockNumber, + blockTimestamp: blockTimestamp, blockHash: blockHash, txHash: txHash, txIdx: txIndex, @@ -115,14 +117,15 @@ func (t *tracer) onLog(log *types.Log) { func (t *tracer) captureLog(address common.Address, topics []common.Hash, data []byte) { t.logs[len(t.logs)-1] = append(t.logs[len(t.logs)-1], &types.Log{ - Address: address, - Topics: topics, - Data: data, - BlockNumber: t.blockNumber, - BlockHash: t.blockHash, - TxHash: t.txHash, - TxIndex: t.txIdx, - Index: uint(t.count), + Address: address, + Topics: topics, + Data: data, + BlockNumber: t.blockNumber, + BlockTimestamp: t.blockTimestamp, + BlockHash: t.blockHash, + TxHash: t.txHash, + TxIndex: t.txIdx, + Index: uint(t.count), }) t.count++ } diff --git a/internal/ethapi/simulate.go b/internal/ethapi/simulate.go index 2bda69b315..0d1a59b371 100644 --- a/internal/ethapi/simulate.go +++ b/internal/ethapi/simulate.go @@ -244,7 +244,7 @@ func (sim *simulator) processBlock(ctx context.Context, block *simBlock, header, callResults = make([]simCallResult, len(block.Calls)) receipts = make([]*types.Receipt, len(block.Calls)) // Block hash will be repaired after execution. - tracer = newTracer(sim.traceTransfers, blockContext.BlockNumber.Uint64(), common.Hash{}, common.Hash{}, 0) + tracer = newTracer(sim.traceTransfers, blockContext.BlockNumber.Uint64(), blockContext.Time, common.Hash{}, common.Hash{}, 0) vmConfig = &vm.Config{ NoBaseFee: !sim.validate, Tracer: tracer.Hooks(), From ee309827c680d4efc8f9cebf82017bdbec6be051 Mon Sep 17 00:00:00 2001 From: Sina M <1591639+s1na@users.noreply.github.com> Date: Tue, 7 Oct 2025 11:24:30 +0200 Subject: [PATCH 30/61] build: faster gh actions workflow, no ubuntu on appveyor (#32829) This PR does a few things: - Sets the gh actions runner sizes for lint (s) and test (l) workflows - Runs the tests on gh actions in parallel - Skips fetching the spec tests when unnecessary (on windows in appveyor) - Removes ubuntu appveyor runner since it's essentially duplicate of the gh action workflow now The gh test seems to go down from ~35min to ~13min. --- .github/workflows/go.yml | 6 +++--- appveyor.yml | 22 +--------------------- build/ci.go | 28 +++++++++++++++++----------- 3 files changed, 21 insertions(+), 35 deletions(-) diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index cc8ea36d74..b8cf7f75e0 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -10,7 +10,7 @@ on: jobs: lint: name: Lint - runs-on: self-hosted-ghr + runs-on: [self-hosted-ghr, size-s-x64] steps: - uses: actions/checkout@v4 with: @@ -37,7 +37,7 @@ jobs: test: name: Test needs: lint - runs-on: self-hosted-ghr + runs-on: [self-hosted-ghr, size-l-x64] strategy: matrix: go: @@ -55,4 +55,4 @@ jobs: cache: false - name: Run tests - run: go run build/ci.go test + run: go run build/ci.go test -p 8 diff --git a/appveyor.yml b/appveyor.yml index ae1c74c18e..8dce7f30a2 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -2,7 +2,6 @@ clone_depth: 5 version: "{branch}.{build}" image: - - Ubuntu - Visual Studio 2019 environment: @@ -17,25 +16,6 @@ install: - go version for: - # Linux has its own script without -arch and -cc. - # The linux builder also runs lint. - - matrix: - only: - - image: Ubuntu - build_script: - - go run build/ci.go lint - - go run build/ci.go check_generate - - go run build/ci.go check_baddeps - - go run build/ci.go install -dlgo - test_script: - - go run build/ci.go test -dlgo -short - - # linux/386 is disabled. - - matrix: - exclude: - - image: Ubuntu - GETH_ARCH: 386 - # Windows builds for amd64 + 386. - matrix: only: @@ -56,4 +36,4 @@ for: - go run build/ci.go archive -arch %GETH_ARCH% -type zip -signer WINDOWS_SIGNING_KEY -upload gethstore/builds - go run build/ci.go nsis -arch %GETH_ARCH% -signer WINDOWS_SIGNING_KEY -upload gethstore/builds test_script: - - go run build/ci.go test -dlgo -arch %GETH_ARCH% -cc %GETH_CC% -short + - go run build/ci.go test -dlgo -arch %GETH_ARCH% -cc %GETH_CC% -short -skip-spectests diff --git a/build/ci.go b/build/ci.go index da867a1516..905f6e4072 100644 --- a/build/ci.go +++ b/build/ci.go @@ -281,20 +281,26 @@ func buildFlags(env build.Environment, staticLinking bool, buildTags []string) ( func doTest(cmdline []string) { var ( - dlgo = flag.Bool("dlgo", false, "Download Go and build with it") - arch = flag.String("arch", "", "Run tests for given architecture") - cc = flag.String("cc", "", "Sets C compiler binary") - coverage = flag.Bool("coverage", false, "Whether to record code coverage") - verbose = flag.Bool("v", false, "Whether to log verbosely") - race = flag.Bool("race", false, "Execute the race detector") - short = flag.Bool("short", false, "Pass the 'short'-flag to go test") - cachedir = flag.String("cachedir", "./build/cache", "directory for caching downloads") + dlgo = flag.Bool("dlgo", false, "Download Go and build with it") + arch = flag.String("arch", "", "Run tests for given architecture") + cc = flag.String("cc", "", "Sets C compiler binary") + coverage = flag.Bool("coverage", false, "Whether to record code coverage") + verbose = flag.Bool("v", false, "Whether to log verbosely") + race = flag.Bool("race", false, "Execute the race detector") + short = flag.Bool("short", false, "Pass the 'short'-flag to go test") + cachedir = flag.String("cachedir", "./build/cache", "directory for caching downloads") + skipspectests = flag.Bool("skip-spectests", false, "Skip downloading execution-spec-tests fixtures") + threads = flag.Int("p", 1, "Number of CPU threads to use for testing") ) flag.CommandLine.Parse(cmdline) - // Get test fixtures. + // Load checksums file (needed for both spec tests and dlgo) csdb := download.MustLoadChecksums("build/checksums.txt") - downloadSpecTestFixtures(csdb, *cachedir) + + // Get test fixtures. + if !*skipspectests { + downloadSpecTestFixtures(csdb, *cachedir) + } // Configure the toolchain. tc := build.GoToolchain{GOARCH: *arch, CC: *cc} @@ -315,7 +321,7 @@ func doTest(cmdline []string) { // Test a single package at a time. CI builders are slow // and some tests run into timeouts under load. - gotest.Args = append(gotest.Args, "-p", "1") + gotest.Args = append(gotest.Args, "-p", fmt.Sprintf("%d", *threads)) if *coverage { gotest.Args = append(gotest.Args, "-covermode=atomic", "-cover") } From d67037a981cbb6355b657d30429af3c325921364 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Wed, 8 Oct 2025 11:14:27 +0200 Subject: [PATCH 31/61] cmd/devp2p/internal/ethtest: update to PoS-only test chain (#32850) --- cmd/devp2p/internal/ethtest/mkchain.sh | 5 +- cmd/devp2p/internal/ethtest/snap.go | 32 +- .../internal/ethtest/testdata/chain.rlp | Bin 341951 -> 451888 bytes .../internal/ethtest/testdata/forkenv.json | 37 +- .../internal/ethtest/testdata/genesis.json | 59 +- .../internal/ethtest/testdata/headblock.json | 21 +- .../internal/ethtest/testdata/headfcu.json | 8 +- .../internal/ethtest/testdata/headstate.json | 5866 +++--- .../internal/ethtest/testdata/newpayload.json | 16955 +++++++++++----- .../internal/ethtest/testdata/txinfo.json | 5058 ++--- 10 files changed, 16880 insertions(+), 11161 deletions(-) diff --git a/cmd/devp2p/internal/ethtest/mkchain.sh b/cmd/devp2p/internal/ethtest/mkchain.sh index b9253e8ca7..fab630d977 100644 --- a/cmd/devp2p/internal/ethtest/mkchain.sh +++ b/cmd/devp2p/internal/ethtest/mkchain.sh @@ -1,9 +1,10 @@ #!/bin/sh hivechain generate \ + --pos \ --fork-interval 6 \ --tx-interval 1 \ - --length 500 \ + --length 600 \ --outdir testdata \ - --lastfork cancun \ + --lastfork prague \ --outputs accounts,genesis,chain,headstate,txinfo,headblock,headfcu,newpayload,forkenv diff --git a/cmd/devp2p/internal/ethtest/snap.go b/cmd/devp2p/internal/ethtest/snap.go index 9c1efa0e8e..f4fce0931f 100644 --- a/cmd/devp2p/internal/ethtest/snap.go +++ b/cmd/devp2p/internal/ethtest/snap.go @@ -86,9 +86,9 @@ func (s *Suite) TestSnapGetAccountRange(t *utesting.T) { root: root, startingHash: zero, limitHash: ffHash, - expAccounts: 86, + expAccounts: 67, expFirst: firstKey, - expLast: common.HexToHash("0x445cb5c1278fdce2f9cbdb681bdd76c52f8e50e41dbd9e220242a69ba99ac099"), + expLast: common.HexToHash("0x622e662246601dd04f996289ce8b85e86db7bb15bb17f86487ec9d543ddb6f9a"), desc: "In this test, we request the entire state range, but limit the response to 4000 bytes.", }, { @@ -96,9 +96,9 @@ func (s *Suite) TestSnapGetAccountRange(t *utesting.T) { root: root, startingHash: zero, limitHash: ffHash, - expAccounts: 65, + expAccounts: 49, expFirst: firstKey, - expLast: common.HexToHash("0x2e6fe1362b3e388184fd7bf08e99e74170b26361624ffd1c5f646da7067b58b6"), + expLast: common.HexToHash("0x445cb5c1278fdce2f9cbdb681bdd76c52f8e50e41dbd9e220242a69ba99ac099"), desc: "In this test, we request the entire state range, but limit the response to 3000 bytes.", }, { @@ -106,9 +106,9 @@ func (s *Suite) TestSnapGetAccountRange(t *utesting.T) { root: root, startingHash: zero, limitHash: ffHash, - expAccounts: 44, + expAccounts: 34, expFirst: firstKey, - expLast: common.HexToHash("0x1c3f74249a4892081ba0634a819aec9ed25f34c7653f5719b9098487e65ab595"), + expLast: common.HexToHash("0x2ef46ebd2073cecde499c2e8df028ad79a26d57bfaa812c4c6f7eb4c9617b913"), desc: "In this test, we request the entire state range, but limit the response to 2000 bytes.", }, { @@ -177,9 +177,9 @@ The server should return the first available account.`, root: root, startingHash: firstKey, limitHash: ffHash, - expAccounts: 86, + expAccounts: 67, expFirst: firstKey, - expLast: common.HexToHash("0x445cb5c1278fdce2f9cbdb681bdd76c52f8e50e41dbd9e220242a69ba99ac099"), + expLast: common.HexToHash("0x622e662246601dd04f996289ce8b85e86db7bb15bb17f86487ec9d543ddb6f9a"), desc: `In this test, startingHash is exactly the first available account key. The server should return the first available account of the state as the first item.`, }, @@ -188,9 +188,9 @@ The server should return the first available account of the state as the first i root: root, startingHash: hashAdd(firstKey, 1), limitHash: ffHash, - expAccounts: 86, + expAccounts: 67, expFirst: secondKey, - expLast: common.HexToHash("0x4615e5f5df5b25349a00ad313c6cd0436b6c08ee5826e33a018661997f85ebaa"), + expLast: common.HexToHash("0x66192e4c757fba1cdc776e6737008f42d50370d3cd801db3624274283bf7cd63"), desc: `In this test, startingHash is after the first available key. The server should return the second account of the state as the first item.`, }, @@ -226,9 +226,9 @@ server to return no data because genesis is older than 127 blocks.`, root: s.chain.RootAt(int(s.chain.Head().Number().Uint64()) - 127), startingHash: zero, limitHash: ffHash, - expAccounts: 84, + expAccounts: 66, expFirst: firstKey, - expLast: common.HexToHash("0x580aa878e2f92d113a12c0a3ce3c21972b03dbe80786858d49a72097e2c491a3"), + expLast: common.HexToHash("0x729953a43ed6c913df957172680a17e5735143ad767bda8f58ac84ec62fbec5e"), desc: `This test requests data at a state root that is 127 blocks old. We expect the server to have this state available.`, }, @@ -657,8 +657,8 @@ The server should reject the request.`, // It's a bit unfortunate these are hard-coded, but the result depends on // a lot of aspects of the state trie and can't be guessed in a simple // way. So you'll have to update this when the test chain is changed. - common.HexToHash("0x3e963a69401a70224cbfb8c0cc2249b019041a538675d71ccf80c9328d114e2e"), - common.HexToHash("0xd0670d09cdfbf3c6320eb3e92c47c57baa6c226551a2d488c05581091e6b1689"), + common.HexToHash("0x5bdc0d6057b35642a16d27223ea5454e5a17a400e28f7328971a5f2a87773b76"), + common.HexToHash("0x0a76c9812ca90ffed8ee4d191e683f93386b6e50cfe3679c0760d27510aa7fc5"), empty, empty, empty, empty, empty, empty, empty, empty, empty, empty, empty, empty, empty, empty, empty, empty, empty, empty, empty, empty, empty, empty, empty, empty, empty, empty, empty, empty, empty, empty, empty, empty, empty, empty, empty, empty, @@ -678,8 +678,8 @@ The server should reject the request.`, // be updated when the test chain is changed. expHashes: []common.Hash{ empty, - common.HexToHash("0xd0670d09cdfbf3c6320eb3e92c47c57baa6c226551a2d488c05581091e6b1689"), - common.HexToHash("0x3e963a69401a70224cbfb8c0cc2249b019041a538675d71ccf80c9328d114e2e"), + common.HexToHash("0x0a76c9812ca90ffed8ee4d191e683f93386b6e50cfe3679c0760d27510aa7fc5"), + common.HexToHash("0x5bdc0d6057b35642a16d27223ea5454e5a17a400e28f7328971a5f2a87773b76"), }, }, diff --git a/cmd/devp2p/internal/ethtest/testdata/chain.rlp b/cmd/devp2p/internal/ethtest/testdata/chain.rlp index 2964c02bb1fb7f695fe6eb9c1c113f9db0b7c97b..7d4f4b3efe5d204312966e56040e759ce53b6600 100644 GIT binary patch literal 451888 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

9J?uR9fRSN| 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

REClOl)IIGpaOOSU*o%(ovM%%c3*qn6q4VI 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

3MfB$6A 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 PgC@ZCACEHDK6>;YypJV? literal 341951 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

BUlu*YmpL9FoEf$3g&@_?xj1~>XJ8%0Z+ETB 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 Date: Wed, 8 Oct 2025 12:29:51 +0300 Subject: [PATCH 32/61] core/rawdb: remove duplicated type storedReceiptRLP (#32820) Co-authored-by: Felix Lange --- core/rawdb/accessors_chain.go | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/core/rawdb/accessors_chain.go b/core/rawdb/accessors_chain.go index 0782a0e7da..f20d675ff8 100644 --- a/core/rawdb/accessors_chain.go +++ b/core/rawdb/accessors_chain.go @@ -664,15 +664,6 @@ func DeleteReceipts(db ethdb.KeyValueWriter, hash common.Hash, number uint64) { } } -// storedReceiptRLP is the storage encoding of a receipt. -// Re-definition in core/types/receipt.go. -// TODO: Re-use the existing definition. -type storedReceiptRLP struct { - PostStateOrStatus []byte - CumulativeGasUsed uint64 - Logs []*types.Log -} - // ReceiptLogs is a barebone version of ReceiptForStorage which only keeps // the list of logs. When decoding a stored receipt into this object we // avoid creating the bloom filter. @@ -682,11 +673,11 @@ type receiptLogs struct { // DecodeRLP implements rlp.Decoder. func (r *receiptLogs) DecodeRLP(s *rlp.Stream) error { - var stored storedReceiptRLP - if err := s.Decode(&stored); err != nil { + var rs types.ReceiptForStorage + if err := rs.DecodeRLP(s); err != nil { return err } - r.Logs = stored.Logs + r.Logs = rs.Logs return nil } From 064ab701ea98dd4e32b331318ca7f2cc5e5edcaa Mon Sep 17 00:00:00 2001 From: Nicolas Gotchac Date: Wed, 8 Oct 2025 12:50:03 +0200 Subject: [PATCH 33/61] eth/protocols/eth: use BlockChain interface in Handshake (#32847) --- eth/protocols/eth/handshake.go | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/eth/protocols/eth/handshake.go b/eth/protocols/eth/handshake.go index 824e49fb2b..bb3d1b8eb4 100644 --- a/eth/protocols/eth/handshake.go +++ b/eth/protocols/eth/handshake.go @@ -22,7 +22,6 @@ import ( "time" "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/forkid" "github.com/ethereum/go-ethereum/metrics" "github.com/ethereum/go-ethereum/p2p" @@ -36,7 +35,7 @@ const ( // Handshake executes the eth protocol handshake, negotiating version number, // network IDs, difficulties, head and genesis blocks. -func (p *Peer) Handshake(networkID uint64, chain *core.BlockChain, rangeMsg BlockRangeUpdatePacket) error { +func (p *Peer) Handshake(networkID uint64, chain forkid.Blockchain, rangeMsg BlockRangeUpdatePacket) error { switch p.version { case ETH69: return p.handshake69(networkID, chain, rangeMsg) @@ -47,10 +46,10 @@ func (p *Peer) Handshake(networkID uint64, chain *core.BlockChain, rangeMsg Bloc } } -func (p *Peer) handshake68(networkID uint64, chain *core.BlockChain) error { +func (p *Peer) handshake68(networkID uint64, chain forkid.Blockchain) error { var ( genesis = chain.Genesis() - latest = chain.CurrentBlock() + latest = chain.CurrentHeader() forkID = forkid.NewID(chain.Config(), genesis, latest.Number.Uint64(), latest.Time) forkFilter = forkid.NewFilter(chain) ) @@ -92,10 +91,10 @@ func (p *Peer) readStatus68(networkID uint64, status *StatusPacket68, genesis co return nil } -func (p *Peer) handshake69(networkID uint64, chain *core.BlockChain, rangeMsg BlockRangeUpdatePacket) error { +func (p *Peer) handshake69(networkID uint64, chain forkid.Blockchain, rangeMsg BlockRangeUpdatePacket) error { var ( genesis = chain.Genesis() - latest = chain.CurrentBlock() + latest = chain.CurrentHeader() forkID = forkid.NewID(chain.Config(), genesis, latest.Number.Uint64(), latest.Time) forkFilter = forkid.NewFilter(chain) ) From e42af536c552538a8ca8c353a26669fd2e9eb150 Mon Sep 17 00:00:00 2001 From: Alexey Osipov Date: Wed, 8 Oct 2025 20:23:44 +0300 Subject: [PATCH 34/61] cmd/devp2p/internal/ethtest: accept responses in any order (#32834) In both `TestSimultaneousRequests` and `TestSameRequestID`, we send two concurrent requests. The client under test is free to respond in either order, so we need to handle responses both ways. Also fixes an issue where some generated blob transactions didn't have any blobs. --------- Co-authored-by: Felix Lange --- cmd/devp2p/internal/ethtest/protocol.go | 6 ++ cmd/devp2p/internal/ethtest/suite.go | 97 +++++++++++++++---------- 2 files changed, 63 insertions(+), 40 deletions(-) diff --git a/cmd/devp2p/internal/ethtest/protocol.go b/cmd/devp2p/internal/ethtest/protocol.go index a21d1ca7a1..af76082318 100644 --- a/cmd/devp2p/internal/ethtest/protocol.go +++ b/cmd/devp2p/internal/ethtest/protocol.go @@ -86,3 +86,9 @@ func protoOffset(proto Proto) uint64 { panic("unhandled protocol") } } + +// msgTypePtr is the constraint for protocol message types. +type msgTypePtr[U any] interface { + *U + Kind() byte +} diff --git a/cmd/devp2p/internal/ethtest/suite.go b/cmd/devp2p/internal/ethtest/suite.go index 47327b6844..c23360bf82 100644 --- a/cmd/devp2p/internal/ethtest/suite.go +++ b/cmd/devp2p/internal/ethtest/suite.go @@ -196,6 +196,7 @@ to check if the node disconnects after receiving multiple invalid requests.`) func (s *Suite) TestSimultaneousRequests(t *utesting.T) { t.Log(`This test requests blocks headers from the node, performing two requests concurrently, with different request IDs.`) + conn, err := s.dialAndPeer(nil) if err != nil { t.Fatalf("peering failed: %v", err) @@ -235,37 +236,36 @@ concurrently, with different request IDs.`) } // Wait for responses. - headers1 := new(eth.BlockHeadersPacket) - if err := conn.ReadMsg(ethProto, eth.BlockHeadersMsg, &headers1); err != nil { - t.Fatalf("error reading block headers msg: %v", err) - } - if got, want := headers1.RequestId, req1.RequestId; got != want { - t.Fatalf("unexpected request id in response: got %d, want %d", got, want) - } - headers2 := new(eth.BlockHeadersPacket) - if err := conn.ReadMsg(ethProto, eth.BlockHeadersMsg, &headers2); err != nil { - t.Fatalf("error reading block headers msg: %v", err) - } - if got, want := headers2.RequestId, req2.RequestId; got != want { - t.Fatalf("unexpected request id in response: got %d, want %d", got, want) + // Note they can arrive in either order. + resp, err := collectResponses(conn, 2, func(msg *eth.BlockHeadersPacket) uint64 { + if msg.RequestId != 111 && msg.RequestId != 222 { + t.Fatalf("response with unknown request ID: %v", msg.RequestId) + } + return msg.RequestId + }) + if err != nil { + t.Fatal(err) } - // Check received headers for accuracy. + // Check if headers match. + resp1 := resp[111] if expected, err := s.chain.GetHeaders(req1); err != nil { t.Fatalf("failed to get expected headers for request 1: %v", err) - } else if !headersMatch(expected, headers1.BlockHeadersRequest) { - t.Fatalf("header mismatch: \nexpected %v \ngot %v", expected, headers1) + } else if !headersMatch(expected, resp1.BlockHeadersRequest) { + t.Fatalf("header mismatch for request ID %v: \nexpected %v \ngot %v", 111, expected, resp1) } + resp2 := resp[222] if expected, err := s.chain.GetHeaders(req2); err != nil { t.Fatalf("failed to get expected headers for request 2: %v", err) - } else if !headersMatch(expected, headers2.BlockHeadersRequest) { - t.Fatalf("header mismatch: \nexpected %v \ngot %v", expected, headers2) + } else if !headersMatch(expected, resp2.BlockHeadersRequest) { + t.Fatalf("header mismatch for request ID %v: \nexpected %v \ngot %v", 222, expected, resp2) } } func (s *Suite) TestSameRequestID(t *utesting.T) { t.Log(`This test requests block headers, performing two concurrent requests with the same request ID. The node should handle the request by responding to both requests.`) + conn, err := s.dialAndPeer(nil) if err != nil { t.Fatalf("peering failed: %v", err) @@ -289,7 +289,7 @@ same request ID. The node should handle the request by responding to both reques Origin: eth.HashOrNumber{ Number: 33, }, - Amount: 2, + Amount: 3, }, } @@ -301,33 +301,50 @@ same request ID. The node should handle the request by responding to both reques t.Fatalf("failed to write to connection: %v", err) } - // Wait for the responses. - headers1 := new(eth.BlockHeadersPacket) - if err := conn.ReadMsg(ethProto, eth.BlockHeadersMsg, &headers1); err != nil { - t.Fatalf("error reading from connection: %v", err) - } - if got, want := headers1.RequestId, request1.RequestId; got != want { - t.Fatalf("unexpected request id: got %d, want %d", got, want) - } - headers2 := new(eth.BlockHeadersPacket) - if err := conn.ReadMsg(ethProto, eth.BlockHeadersMsg, &headers2); err != nil { - t.Fatalf("error reading from connection: %v", err) - } - if got, want := headers2.RequestId, request2.RequestId; got != want { - t.Fatalf("unexpected request id: got %d, want %d", got, want) + // Wait for the responses. They can arrive in either order, and we can't tell them + // apart by their request ID, so use the number of headers instead. + resp, err := collectResponses(conn, 2, func(msg *eth.BlockHeadersPacket) uint64 { + id := uint64(len(msg.BlockHeadersRequest)) + if id != 2 && id != 3 { + t.Fatalf("invalid number of headers in response: %d", id) + } + return id + }) + if err != nil { + t.Fatal(err) } // Check if headers match. + resp1 := resp[2] if expected, err := s.chain.GetHeaders(request1); err != nil { - t.Fatalf("failed to get expected block headers: %v", err) - } else if !headersMatch(expected, headers1.BlockHeadersRequest) { - t.Fatalf("header mismatch: \nexpected %v \ngot %v", expected, headers1) + t.Fatalf("failed to get expected headers for request 1: %v", err) + } else if !headersMatch(expected, resp1.BlockHeadersRequest) { + t.Fatalf("headers mismatch: \nexpected %v \ngot %v", expected, resp1) } + resp2 := resp[3] if expected, err := s.chain.GetHeaders(request2); err != nil { - t.Fatalf("failed to get expected block headers: %v", err) - } else if !headersMatch(expected, headers2.BlockHeadersRequest) { - t.Fatalf("header mismatch: \nexpected %v \ngot %v", expected, headers2) + t.Fatalf("failed to get expected headers for request 2: %v", err) + } else if !headersMatch(expected, resp2.BlockHeadersRequest) { + t.Fatalf("headers mismatch: \nexpected %v \ngot %v", expected, resp2) + } +} + +// collectResponses waits for n messages of type T on the given connection. +// The messsages are collected according to the 'identity' function. +func collectResponses[T any, P msgTypePtr[T]](conn *Conn, n int, identity func(P) uint64) (map[uint64]P, error) { + resp := make(map[uint64]P, n) + for range n { + r := new(T) + if err := conn.ReadMsg(ethProto, eth.BlockHeadersMsg, r); err != nil { + return resp, fmt.Errorf("read error: %v", err) + } + id := identity(r) + if resp[id] != nil { + return resp, fmt.Errorf("duplicate response %v", r) + } + resp[id] = r } + return resp, nil } func (s *Suite) TestZeroRequestID(t *utesting.T) { @@ -887,7 +904,7 @@ func (s *Suite) makeBlobTxs(count, blobs int, discriminator byte) (txs types.Tra from, nonce := s.chain.GetSender(5) for i := 0; i < count; i++ { // Make blob data, max of 2 blobs per tx. - blobdata := make([]byte, blobs%3) + blobdata := make([]byte, min(blobs, 2)) for i := range blobdata { blobdata[i] = discriminator blobs -= 1 From 695c1445ab4eb26b630641a7bf6c27fceaa5fc2b Mon Sep 17 00:00:00 2001 From: phrwlk Date: Thu, 9 Oct 2025 03:59:06 +0300 Subject: [PATCH 35/61] core/rawdb: correct misleading comments for state history accessors (#32783) --- core/rawdb/accessors_state.go | 31 +++++++++++++++++++------------ 1 file changed, 19 insertions(+), 12 deletions(-) diff --git a/core/rawdb/accessors_state.go b/core/rawdb/accessors_state.go index 2359fb18f1..46aa5fd070 100644 --- a/core/rawdb/accessors_state.go +++ b/core/rawdb/accessors_state.go @@ -170,9 +170,11 @@ func ReadStateHistoryMetaList(db ethdb.AncientReaderOp, start uint64, count uint return db.AncientRange(stateHistoryMeta, start-1, count, 0) } -// ReadStateAccountIndex retrieves the state root corresponding to the specified -// state history. Compute the position of state history in freezer by minus one -// since the id of first state history starts from one(zero for initial state). +// ReadStateAccountIndex retrieves the account index blob for the specified +// state history. The index contains fixed-size entries with offsets and lengths +// into the concatenated account data table. Compute the position of state +// history in freezer by minus one since the id of first state history starts +// from one (zero for initial state). func ReadStateAccountIndex(db ethdb.AncientReaderOp, id uint64) []byte { blob, err := db.Ancient(stateHistoryAccountIndex, id-1) if err != nil { @@ -181,9 +183,11 @@ func ReadStateAccountIndex(db ethdb.AncientReaderOp, id uint64) []byte { return blob } -// ReadStateStorageIndex retrieves the state root corresponding to the specified -// state history. Compute the position of state history in freezer by minus one -// since the id of first state history starts from one(zero for initial state). +// ReadStateStorageIndex retrieves the storage index blob for the specified +// state history. The index contains fixed-size entries that locate storage slot +// data in the concatenated storage data table. Compute the position of state +// history in freezer by minus one since the id of first state history starts +// from one (zero for initial state). func ReadStateStorageIndex(db ethdb.AncientReaderOp, id uint64) []byte { blob, err := db.Ancient(stateHistoryStorageIndex, id-1) if err != nil { @@ -192,9 +196,10 @@ func ReadStateStorageIndex(db ethdb.AncientReaderOp, id uint64) []byte { return blob } -// ReadStateAccountHistory retrieves the state root corresponding to the specified -// state history. Compute the position of state history in freezer by minus one -// since the id of first state history starts from one(zero for initial state). +// ReadStateAccountHistory retrieves the concatenated account data blob for the +// specified state history. Offsets and lengths are resolved via the account +// index. Compute the position of state history in freezer by minus one since +// the id of first state history starts from one (zero for initial state). func ReadStateAccountHistory(db ethdb.AncientReaderOp, id uint64) []byte { blob, err := db.Ancient(stateHistoryAccountData, id-1) if err != nil { @@ -203,9 +208,11 @@ func ReadStateAccountHistory(db ethdb.AncientReaderOp, id uint64) []byte { return blob } -// ReadStateStorageHistory retrieves the state root corresponding to the specified -// state history. Compute the position of state history in freezer by minus one -// since the id of first state history starts from one(zero for initial state). +// ReadStateStorageHistory retrieves the concatenated storage slot data blob for +// the specified state history. Locations are resolved via the account and +// storage indexes. Compute the position of state history in freezer by minus +// one since the id of first state history starts from one (zero for initial +// state). func ReadStateStorageHistory(db ethdb.AncientReaderOp, id uint64) []byte { blob, err := db.Ancient(stateHistoryStorageData, id-1) if err != nil { From a1b8e4880d24d5e618b0637defa380d75e30c831 Mon Sep 17 00:00:00 2001 From: CertiK <138698582+CertiK-Geth@users.noreply.github.com> Date: Thu, 9 Oct 2025 17:34:30 +0800 Subject: [PATCH 36/61] eth/filters: terminate pending tx subscription on error (#32794) Fixes issue #32793. When the pending tx subscription ends, the filter is removed from `api.filters`, but it is not terminated. There is no other way to terminate it, so the subscription will leak, and potentially block the producer side. --- eth/filters/api.go | 1 + 1 file changed, 1 insertion(+) diff --git a/eth/filters/api.go b/eth/filters/api.go index d678c40389..334a19f0b5 100644 --- a/eth/filters/api.go +++ b/eth/filters/api.go @@ -152,6 +152,7 @@ func (api *FilterAPI) NewPendingTransactionFilter(fullTx *bool) rpc.ID { api.filtersMu.Lock() delete(api.filters, pendingTxSub.ID) api.filtersMu.Unlock() + pendingTxSub.Unsubscribe() return } } From 11208553ddded439c708fa430e559675f25052e7 Mon Sep 17 00:00:00 2001 From: 10gic Date: Thu, 9 Oct 2025 20:14:53 +0800 Subject: [PATCH 37/61] eth/filters: add `transactionReceipts` subscription (#32697) - Introduce a new subscription kind `transactionReceipts` to allow clients to receive transaction receipts over WebSocket as soon as they are available. - Accept optional `transactionHashes` filter to subscribe to receipts for specific transactions; an empty or omitted filter subscribes to all receipts. - Preserve the same receipt format as returned by `eth_getTransactionReceipt`. - Avoid additional HTTP polling, reducing RPC load and latency. --------- Co-authored-by: Sina Mahmoodi --- core/blockchain.go | 26 +++++- core/events.go | 4 +- eth/filters/api.go | 68 ++++++++++++++ eth/filters/filter.go | 68 ++++++++++++++ eth/filters/filter_system.go | 35 ++++++++ eth/filters/filter_system_test.go | 141 ++++++++++++++++++++++++++++++ internal/ethapi/api.go | 8 +- 7 files changed, 341 insertions(+), 9 deletions(-) diff --git a/core/blockchain.go b/core/blockchain.go index 71eb4c45a2..b7acd12aca 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -1690,7 +1690,12 @@ func (bc *BlockChain) writeBlockAndSetHead(block *types.Block, receipts []*types // Set new head. bc.writeHeadBlock(block) - bc.chainFeed.Send(ChainEvent{Header: block.Header()}) + bc.chainFeed.Send(ChainEvent{ + Header: block.Header(), + Receipts: receipts, + Transactions: block.Transactions(), + }) + if len(logs) > 0 { bc.logsFeed.Send(logs) } @@ -2342,6 +2347,13 @@ func (bc *BlockChain) recoverAncestors(block *types.Block, makeWitness bool) (co // collectLogs collects the logs that were generated or removed during the // processing of a block. These logs are later announced as deleted or reborn. func (bc *BlockChain) collectLogs(b *types.Block, removed bool) []*types.Log { + _, logs := bc.collectReceiptsAndLogs(b, removed) + return logs +} + +// collectReceiptsAndLogs retrieves receipts from the database and returns both receipts and logs. +// This avoids duplicate database reads when both are needed. +func (bc *BlockChain) collectReceiptsAndLogs(b *types.Block, removed bool) ([]*types.Receipt, []*types.Log) { var blobGasPrice *big.Int if b.ExcessBlobGas() != nil { blobGasPrice = eip4844.CalcBlobFee(bc.chainConfig, b.Header()) @@ -2359,7 +2371,7 @@ func (bc *BlockChain) collectLogs(b *types.Block, removed bool) []*types.Log { logs = append(logs, log) } } - return logs + return receipts, logs } // reorg takes two blocks, an old chain and a new chain and will reconstruct the @@ -2588,8 +2600,14 @@ func (bc *BlockChain) SetCanonical(head *types.Block) (common.Hash, error) { bc.writeHeadBlock(head) // Emit events - logs := bc.collectLogs(head, false) - bc.chainFeed.Send(ChainEvent{Header: head.Header()}) + receipts, logs := bc.collectReceiptsAndLogs(head, false) + + bc.chainFeed.Send(ChainEvent{ + Header: head.Header(), + Receipts: receipts, + Transactions: head.Transactions(), + }) + if len(logs) > 0 { bc.logsFeed.Send(logs) } diff --git a/core/events.go b/core/events.go index 5ad2cb1f7b..ef0de32426 100644 --- a/core/events.go +++ b/core/events.go @@ -27,7 +27,9 @@ type NewTxsEvent struct{ Txs []*types.Transaction } type RemovedLogsEvent struct{ Logs []*types.Log } type ChainEvent struct { - Header *types.Header + Header *types.Header + Receipts []*types.Receipt + Transactions []*types.Transaction } type ChainHeadEvent struct { diff --git a/eth/filters/api.go b/eth/filters/api.go index 334a19f0b5..9f9209aea7 100644 --- a/eth/filters/api.go +++ b/eth/filters/api.go @@ -43,6 +43,7 @@ var ( errPendingLogsUnsupported = errors.New("pending logs are not supported") errExceedMaxTopics = errors.New("exceed max topics") errExceedLogQueryLimit = errors.New("exceed max addresses or topics per search position") + errExceedMaxTxHashes = errors.New("exceed max number of transaction hashes allowed per transactionReceipts subscription") ) const ( @@ -50,6 +51,8 @@ const ( maxTopics = 4 // The maximum number of allowed topics within a topic criteria maxSubTopics = 1000 + // The maximum number of transaction hash criteria allowed in a single subscription + maxTxHashes = 200 ) // filter is a helper struct that holds meta information over the filter type @@ -296,6 +299,71 @@ func (api *FilterAPI) Logs(ctx context.Context, crit FilterCriteria) (*rpc.Subsc return rpcSub, nil } +// TransactionReceiptsFilter defines criteria for transaction receipts subscription. +// If TransactionHashes is nil or empty, receipts for all transactions included in new blocks will be delivered. +// Otherwise, only receipts for the specified transactions will be delivered. +type TransactionReceiptsFilter struct { + TransactionHashes []common.Hash `json:"transactionHashes,omitempty"` +} + +// TransactionReceipts creates a subscription that fires transaction receipts when transactions are included in blocks. +func (api *FilterAPI) TransactionReceipts(ctx context.Context, filter *TransactionReceiptsFilter) (*rpc.Subscription, error) { + notifier, supported := rpc.NotifierFromContext(ctx) + if !supported { + return &rpc.Subscription{}, rpc.ErrNotificationsUnsupported + } + + // Validate transaction hashes limit + if filter != nil && len(filter.TransactionHashes) > maxTxHashes { + return nil, errExceedMaxTxHashes + } + + var ( + rpcSub = notifier.CreateSubscription() + matchedReceipts = make(chan []*ReceiptWithTx) + txHashes []common.Hash + ) + + if filter != nil { + txHashes = filter.TransactionHashes + } + + receiptsSub := api.events.SubscribeTransactionReceipts(txHashes, matchedReceipts) + + go func() { + defer receiptsSub.Unsubscribe() + + signer := types.LatestSigner(api.sys.backend.ChainConfig()) + + for { + select { + case receiptsWithTxs := <-matchedReceipts: + if len(receiptsWithTxs) > 0 { + // Convert to the same format as eth_getTransactionReceipt + marshaledReceipts := make([]map[string]interface{}, len(receiptsWithTxs)) + for i, receiptWithTx := range receiptsWithTxs { + marshaledReceipts[i] = ethapi.MarshalReceipt( + receiptWithTx.Receipt, + receiptWithTx.Receipt.BlockHash, + receiptWithTx.Receipt.BlockNumber.Uint64(), + signer, + receiptWithTx.Transaction, + int(receiptWithTx.Receipt.TransactionIndex), + ) + } + + // Send a batch of tx receipts in one notification + notifier.Notify(rpcSub.ID, marshaledReceipts) + } + case <-rpcSub.Err(): + return + } + } + }() + + return rpcSub, nil +} + // FilterCriteria represents a request to create a new filter. // Same as ethereum.FilterQuery but with UnmarshalJSON() method. type FilterCriteria ethereum.FilterQuery diff --git a/eth/filters/filter.go b/eth/filters/filter.go index 1a9918d0ee..02399bc801 100644 --- a/eth/filters/filter.go +++ b/eth/filters/filter.go @@ -25,6 +25,7 @@ import ( "time" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/filtermaps" "github.com/ethereum/go-ethereum/core/history" "github.com/ethereum/go-ethereum/core/types" @@ -551,3 +552,70 @@ func bloomFilter(bloom types.Bloom, addresses []common.Address, topics [][]commo } return true } + +// ReceiptWithTx contains a receipt and its corresponding transaction +type ReceiptWithTx struct { + Receipt *types.Receipt + Transaction *types.Transaction +} + +// filterReceipts returns the receipts matching the given criteria +// In addition to returning receipts, it also returns the corresponding transactions. +// This is because receipts only contain low-level data, while user-facing data +// may require additional information from the Transaction. +func filterReceipts(txHashes []common.Hash, ev core.ChainEvent) []*ReceiptWithTx { + var ret []*ReceiptWithTx + + receipts := ev.Receipts + txs := ev.Transactions + + if len(receipts) != len(txs) { + log.Warn("Receipts and transactions length mismatch", "receipts", len(receipts), "transactions", len(txs)) + return ret + } + + if len(txHashes) == 0 { + // No filter, send all receipts with their transactions. + ret = make([]*ReceiptWithTx, len(receipts)) + for i, receipt := range receipts { + ret[i] = &ReceiptWithTx{ + Receipt: receipt, + Transaction: txs[i], + } + } + } else if len(txHashes) == 1 { + // Filter by single transaction hash. + // This is a common case, so we distinguish it from filtering by multiple tx hashes and made a small optimization. + for i, receipt := range receipts { + if receipt.TxHash == txHashes[0] { + ret = append(ret, &ReceiptWithTx{ + Receipt: receipt, + Transaction: txs[i], + }) + break + } + } + } else { + // Filter by multiple transaction hashes. + txHashMap := make(map[common.Hash]bool, len(txHashes)) + for _, hash := range txHashes { + txHashMap[hash] = true + } + + for i, receipt := range receipts { + if txHashMap[receipt.TxHash] { + ret = append(ret, &ReceiptWithTx{ + Receipt: receipt, + Transaction: txs[i], + }) + + // Early exit if all receipts are found + if len(ret) == len(txHashes) { + break + } + } + } + } + + return ret +} diff --git a/eth/filters/filter_system.go b/eth/filters/filter_system.go index ecf1c870c1..02783fa5ec 100644 --- a/eth/filters/filter_system.go +++ b/eth/filters/filter_system.go @@ -158,6 +158,8 @@ const ( PendingTransactionsSubscription // BlocksSubscription queries hashes for blocks that are imported BlocksSubscription + // TransactionReceiptsSubscription queries for transaction receipts when transactions are included in blocks + TransactionReceiptsSubscription // LastIndexSubscription keeps track of the last index LastIndexSubscription ) @@ -182,6 +184,8 @@ type subscription struct { logs chan []*types.Log txs chan []*types.Transaction headers chan *types.Header + receipts chan []*ReceiptWithTx + txHashes []common.Hash // contains transaction hashes for transactionReceipts subscription filtering installed chan struct{} // closed when the filter is installed err chan error // closed when the filter is uninstalled } @@ -268,6 +272,7 @@ func (sub *Subscription) Unsubscribe() { case <-sub.f.logs: case <-sub.f.txs: case <-sub.f.headers: + case <-sub.f.receipts: } } @@ -353,6 +358,7 @@ func (es *EventSystem) subscribeLogs(crit ethereum.FilterQuery, logs chan []*typ logs: logs, txs: make(chan []*types.Transaction), headers: make(chan *types.Header), + receipts: make(chan []*ReceiptWithTx), installed: make(chan struct{}), err: make(chan error), } @@ -369,6 +375,7 @@ func (es *EventSystem) SubscribeNewHeads(headers chan *types.Header) *Subscripti logs: make(chan []*types.Log), txs: make(chan []*types.Transaction), headers: headers, + receipts: make(chan []*ReceiptWithTx), installed: make(chan struct{}), err: make(chan error), } @@ -385,6 +392,26 @@ func (es *EventSystem) SubscribePendingTxs(txs chan []*types.Transaction) *Subsc logs: make(chan []*types.Log), txs: txs, headers: make(chan *types.Header), + receipts: make(chan []*ReceiptWithTx), + installed: make(chan struct{}), + err: make(chan error), + } + return es.subscribe(sub) +} + +// SubscribeTransactionReceipts creates a subscription that writes transaction receipts for +// transactions when they are included in blocks. If txHashes is provided, only receipts +// for those specific transaction hashes will be delivered. +func (es *EventSystem) SubscribeTransactionReceipts(txHashes []common.Hash, receipts chan []*ReceiptWithTx) *Subscription { + sub := &subscription{ + id: rpc.NewID(), + typ: TransactionReceiptsSubscription, + created: time.Now(), + logs: make(chan []*types.Log), + txs: make(chan []*types.Transaction), + headers: make(chan *types.Header), + receipts: receipts, + txHashes: txHashes, installed: make(chan struct{}), err: make(chan error), } @@ -415,6 +442,14 @@ func (es *EventSystem) handleChainEvent(filters filterIndex, ev core.ChainEvent) for _, f := range filters[BlocksSubscription] { f.headers <- ev.Header } + + // Handle transaction receipts subscriptions when a new block is added + for _, f := range filters[TransactionReceiptsSubscription] { + matchedReceipts := filterReceipts(f.txHashes, ev) + if len(matchedReceipts) > 0 { + f.receipts <- matchedReceipts + } + } } // eventLoop (un)installs filters and processes mux events. diff --git a/eth/filters/filter_system_test.go b/eth/filters/filter_system_test.go index 0048e74995..e5a1a2b25f 100644 --- a/eth/filters/filter_system_test.go +++ b/eth/filters/filter_system_test.go @@ -31,6 +31,7 @@ import ( "github.com/ethereum/go-ethereum/core/filtermaps" "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/event" "github.com/ethereum/go-ethereum/internal/ethapi" @@ -781,3 +782,143 @@ func TestPendingTxFilterDeadlock(t *testing.T) { } } } + +// TestTransactionReceiptsSubscription tests the transaction receipts subscription functionality +func TestTransactionReceiptsSubscription(t *testing.T) { + t.Parallel() + + const txNum = 5 + + // Setup test environment + var ( + db = rawdb.NewMemoryDatabase() + backend, sys = newTestFilterSystem(db, Config{}) + api = NewFilterAPI(sys) + key1, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") + addr1 = crypto.PubkeyToAddress(key1.PublicKey) + signer = types.NewLondonSigner(big.NewInt(1)) + genesis = &core.Genesis{ + Alloc: types.GenesisAlloc{addr1: {Balance: big.NewInt(1000000000000000000)}}, // 1 ETH + Config: params.TestChainConfig, + BaseFee: big.NewInt(params.InitialBaseFee), + } + _, chain, _ = core.GenerateChainWithGenesis(genesis, ethash.NewFaker(), 1, func(i int, gen *core.BlockGen) { + // Add transactions to the block + for j := 0; j < txNum; j++ { + toAddr := common.HexToAddress("0xb794f5ea0ba39494ce83a213fffba74279579268") + tx, _ := types.SignTx(types.NewTx(&types.LegacyTx{ + Nonce: uint64(j), + GasPrice: gen.BaseFee(), + Gas: 21000, + To: &toAddr, + Value: big.NewInt(1000), + Data: nil, + }), signer, key1) + gen.AddTx(tx) + } + }) + ) + + // Insert the blocks into the chain + blockchain, err := core.NewBlockChain(db, genesis, ethash.NewFaker(), nil) + if err != nil { + t.Fatalf("failed to create tester chain: %v", err) + } + if n, err := blockchain.InsertChain(chain); err != nil { + t.Fatalf("block %d: failed to insert into chain: %v", n, err) + } + + // Prepare test data + receipts := blockchain.GetReceiptsByHash(chain[0].Hash()) + if receipts == nil { + t.Fatalf("failed to get receipts") + } + + chainEvent := core.ChainEvent{ + Header: chain[0].Header(), + Receipts: receipts, + Transactions: chain[0].Transactions(), + } + + txHashes := make([]common.Hash, txNum) + for i := 0; i < txNum; i++ { + txHashes[i] = chain[0].Transactions()[i].Hash() + } + + testCases := []struct { + name string + filterTxHashes []common.Hash + expectedReceiptTxHashes []common.Hash + expectError bool + }{ + { + name: "no filter - should return all receipts", + filterTxHashes: nil, + expectedReceiptTxHashes: txHashes, + expectError: false, + }, + { + name: "single tx hash filter", + filterTxHashes: []common.Hash{txHashes[0]}, + expectedReceiptTxHashes: []common.Hash{txHashes[0]}, + expectError: false, + }, + { + name: "multiple tx hashes filter", + filterTxHashes: []common.Hash{txHashes[0], txHashes[1], txHashes[2]}, + expectedReceiptTxHashes: []common.Hash{txHashes[0], txHashes[1], txHashes[2]}, + expectError: false, + }, + } + + // Run test cases + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + receiptsChan := make(chan []*ReceiptWithTx) + sub := api.events.SubscribeTransactionReceipts(tc.filterTxHashes, receiptsChan) + + // Send chain event + backend.chainFeed.Send(chainEvent) + + // Wait for receipts + timeout := time.After(1 * time.Second) + var receivedReceipts []*types.Receipt + for { + select { + case receiptsWithTx := <-receiptsChan: + for _, receiptWithTx := range receiptsWithTx { + receivedReceipts = append(receivedReceipts, receiptWithTx.Receipt) + } + case <-timeout: + t.Fatalf("timeout waiting for receipts") + } + if len(receivedReceipts) >= len(tc.expectedReceiptTxHashes) { + break + } + } + + // Verify receipt count + if len(receivedReceipts) != len(tc.expectedReceiptTxHashes) { + t.Errorf("Expected %d receipts, got %d", len(tc.expectedReceiptTxHashes), len(receivedReceipts)) + } + + // Verify specific transaction hashes are present + if tc.expectedReceiptTxHashes != nil { + receivedHashes := make(map[common.Hash]bool) + for _, receipt := range receivedReceipts { + receivedHashes[receipt.TxHash] = true + } + + for _, expectedHash := range tc.expectedReceiptTxHashes { + if !receivedHashes[expectedHash] { + t.Errorf("Expected receipt for tx %x not found", expectedHash) + } + } + } + + // Cleanup + sub.Unsubscribe() + <-sub.Err() + }) + } +} diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index c3f267027c..a2cb28d3b2 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -627,7 +627,7 @@ func (api *BlockChainAPI) GetBlockReceipts(ctx context.Context, blockNrOrHash rp result := make([]map[string]interface{}, len(receipts)) for i, receipt := range receipts { - result[i] = marshalReceipt(receipt, block.Hash(), block.NumberU64(), signer, txs[i], i) + result[i] = MarshalReceipt(receipt, block.Hash(), block.NumberU64(), signer, txs[i], i) } return result, nil } @@ -1488,11 +1488,11 @@ func (api *TransactionAPI) GetTransactionReceipt(ctx context.Context, hash commo return nil, err } // Derive the sender. - return marshalReceipt(receipt, blockHash, blockNumber, api.signer, tx, int(index)), nil + return MarshalReceipt(receipt, blockHash, blockNumber, api.signer, tx, int(index)), nil } -// marshalReceipt marshals a transaction receipt into a JSON object. -func marshalReceipt(receipt *types.Receipt, blockHash common.Hash, blockNumber uint64, signer types.Signer, tx *types.Transaction, txIndex int) map[string]interface{} { +// MarshalReceipt marshals a transaction receipt into a JSON object. +func MarshalReceipt(receipt *types.Receipt, blockHash common.Hash, blockNumber uint64, signer types.Signer, tx *types.Transaction, txIndex int) map[string]interface{} { from, _ := types.Sender(signer, tx) fields := map[string]interface{}{ From 4d6d5a3abf7753009255749407c32aefbf3bff78 Mon Sep 17 00:00:00 2001 From: Csaba Kiraly Date: Fri, 10 Oct 2025 07:40:10 +0200 Subject: [PATCH 38/61] core/txpool/legacypool: fix validTxMeter to count transactions (#32845) invalidTxMeter was counting txs, while validTxMeter was counting accounts. Better make the two comparable. --------- Signed-off-by: Csaba Kiraly --- core/txpool/legacypool/legacypool.go | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/core/txpool/legacypool/legacypool.go b/core/txpool/legacypool/legacypool.go index 80a9faf23f..e199d21c7a 100644 --- a/core/txpool/legacypool/legacypool.go +++ b/core/txpool/legacypool/legacypool.go @@ -984,17 +984,24 @@ func (pool *LegacyPool) Add(txs []*types.Transaction, sync bool) []error { // addTxsLocked attempts to queue a batch of transactions if they are valid. // The transaction pool lock must be held. +// Returns the error for each tx, and the set of accounts that might became promotable. func (pool *LegacyPool) addTxsLocked(txs []*types.Transaction) ([]error, *accountSet) { - dirty := newAccountSet(pool.signer) - errs := make([]error, len(txs)) + var ( + dirty = newAccountSet(pool.signer) + errs = make([]error, len(txs)) + valid int64 + ) for i, tx := range txs { replaced, err := pool.add(tx) errs[i] = err - if err == nil && !replaced { - dirty.addTx(tx) + if err == nil { + if !replaced { + dirty.addTx(tx) + } + valid++ } } - validTxMeter.Mark(int64(len(dirty.accounts))) + validTxMeter.Mark(valid) return errs, dirty } From ed264a1f198ea4172b4f2fe622bae5987f114561 Mon Sep 17 00:00:00 2001 From: cui Date: Fri, 10 Oct 2025 13:48:25 +0800 Subject: [PATCH 39/61] eth/protocols/snap: optimize incHash (#32748) --- eth/protocols/snap/range.go | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/eth/protocols/snap/range.go b/eth/protocols/snap/range.go index 8c98c71d50..f32cca8d23 100644 --- a/eth/protocols/snap/range.go +++ b/eth/protocols/snap/range.go @@ -74,8 +74,11 @@ func (r *hashRange) End() common.Hash { // incHash returns the next hash, in lexicographical order (a.k.a plus one) func incHash(h common.Hash) common.Hash { - var a uint256.Int - a.SetBytes32(h[:]) - a.AddUint64(&a, 1) - return common.Hash(a.Bytes32()) + for i := len(h) - 1; i >= 0; i-- { + h[i]++ + if h[i] != 0 { + break + } + } + return h } From de24450dbf0887032eacdee7da130b5e038be5cb Mon Sep 17 00:00:00 2001 From: rjl493456442 Date: Fri, 10 Oct 2025 14:51:27 +0800 Subject: [PATCH 40/61] core/rawdb, triedb/pathdb: introduce trienode history (#32596) It's a pull request based on the #32523 , implementing the structure of trienode history. --- core/rawdb/accessors_history.go | 95 +++- core/rawdb/accessors_state.go | 73 +++ core/rawdb/ancient_scheme.go | 50 +- core/rawdb/schema.go | 43 +- triedb/pathdb/database.go | 4 +- triedb/pathdb/history.go | 57 +- triedb/pathdb/history_index.go | 12 + triedb/pathdb/history_indexer.go | 76 ++- triedb/pathdb/history_state.go | 6 +- triedb/pathdb/history_state_test.go | 20 +- triedb/pathdb/history_trienode.go | 730 ++++++++++++++++++++++++ triedb/pathdb/history_trienode_test.go | 736 +++++++++++++++++++++++++ triedb/pathdb/metrics.go | 21 +- 13 files changed, 1851 insertions(+), 72 deletions(-) create mode 100644 triedb/pathdb/history_trienode.go create mode 100644 triedb/pathdb/history_trienode_test.go diff --git a/core/rawdb/accessors_history.go b/core/rawdb/accessors_history.go index cf1073f387..95a8907edc 100644 --- a/core/rawdb/accessors_history.go +++ b/core/rawdb/accessors_history.go @@ -46,6 +46,27 @@ func DeleteStateHistoryIndexMetadata(db ethdb.KeyValueWriter) { } } +// ReadTrienodeHistoryIndexMetadata retrieves the metadata of trienode history index. +func ReadTrienodeHistoryIndexMetadata(db ethdb.KeyValueReader) []byte { + data, _ := db.Get(headTrienodeHistoryIndexKey) + return data +} + +// WriteTrienodeHistoryIndexMetadata stores the metadata of trienode history index +// into database. +func WriteTrienodeHistoryIndexMetadata(db ethdb.KeyValueWriter, blob []byte) { + if err := db.Put(headTrienodeHistoryIndexKey, blob); err != nil { + log.Crit("Failed to store the metadata of trienode history index", "err", err) + } +} + +// DeleteTrienodeHistoryIndexMetadata removes the metadata of trienode history index. +func DeleteTrienodeHistoryIndexMetadata(db ethdb.KeyValueWriter) { + if err := db.Delete(headTrienodeHistoryIndexKey); err != nil { + log.Crit("Failed to delete the metadata of trienode history index", "err", err) + } +} + // ReadAccountHistoryIndex retrieves the account history index with the provided // account address. func ReadAccountHistoryIndex(db ethdb.KeyValueReader, addressHash common.Hash) []byte { @@ -95,6 +116,30 @@ func DeleteStorageHistoryIndex(db ethdb.KeyValueWriter, addressHash common.Hash, } } +// ReadTrienodeHistoryIndex retrieves the trienode history index with the provided +// account address and storage key hash. +func ReadTrienodeHistoryIndex(db ethdb.KeyValueReader, addressHash common.Hash, path []byte) []byte { + data, err := db.Get(trienodeHistoryIndexKey(addressHash, path)) + if err != nil || len(data) == 0 { + return nil + } + return data +} + +// WriteTrienodeHistoryIndex writes the provided trienode history index into database. +func WriteTrienodeHistoryIndex(db ethdb.KeyValueWriter, addressHash common.Hash, path []byte, data []byte) { + if err := db.Put(trienodeHistoryIndexKey(addressHash, path), data); err != nil { + log.Crit("Failed to store trienode history index", "err", err) + } +} + +// DeleteTrienodeHistoryIndex deletes the specified trienode index from the database. +func DeleteTrienodeHistoryIndex(db ethdb.KeyValueWriter, addressHash common.Hash, path []byte) { + if err := db.Delete(trienodeHistoryIndexKey(addressHash, path)); err != nil { + log.Crit("Failed to delete trienode history index", "err", err) + } +} + // ReadAccountHistoryIndexBlock retrieves the index block with the provided // account address along with the block id. func ReadAccountHistoryIndexBlock(db ethdb.KeyValueReader, addressHash common.Hash, blockID uint32) []byte { @@ -143,6 +188,30 @@ func DeleteStorageHistoryIndexBlock(db ethdb.KeyValueWriter, addressHash common. } } +// ReadTrienodeHistoryIndexBlock retrieves the index block with the provided state +// identifier along with the block id. +func ReadTrienodeHistoryIndexBlock(db ethdb.KeyValueReader, addressHash common.Hash, path []byte, blockID uint32) []byte { + data, err := db.Get(trienodeHistoryIndexBlockKey(addressHash, path, blockID)) + if err != nil || len(data) == 0 { + return nil + } + return data +} + +// WriteTrienodeHistoryIndexBlock writes the provided index block into database. +func WriteTrienodeHistoryIndexBlock(db ethdb.KeyValueWriter, addressHash common.Hash, path []byte, id uint32, data []byte) { + if err := db.Put(trienodeHistoryIndexBlockKey(addressHash, path, id), data); err != nil { + log.Crit("Failed to store trienode index block", "err", err) + } +} + +// DeleteTrienodeHistoryIndexBlock deletes the specified index block from the database. +func DeleteTrienodeHistoryIndexBlock(db ethdb.KeyValueWriter, addressHash common.Hash, path []byte, id uint32) { + if err := db.Delete(trienodeHistoryIndexBlockKey(addressHash, path, id)); err != nil { + log.Crit("Failed to delete trienode index block", "err", err) + } +} + // increaseKey increase the input key by one bit. Return nil if the entire // addition operation overflows. func increaseKey(key []byte) []byte { @@ -155,14 +224,26 @@ func increaseKey(key []byte) []byte { return nil } -// DeleteStateHistoryIndex completely removes all history indexing data, including +// DeleteStateHistoryIndexes completely removes all history indexing data, including // indexes for accounts and storages. -// -// Note, this method assumes the storage space with prefix `StateHistoryIndexPrefix` -// is exclusively occupied by the history indexing data! -func DeleteStateHistoryIndex(db ethdb.KeyValueRangeDeleter) { - start := StateHistoryIndexPrefix - limit := increaseKey(bytes.Clone(StateHistoryIndexPrefix)) +func DeleteStateHistoryIndexes(db ethdb.KeyValueRangeDeleter) { + DeleteHistoryByRange(db, StateHistoryAccountMetadataPrefix) + DeleteHistoryByRange(db, StateHistoryStorageMetadataPrefix) + DeleteHistoryByRange(db, StateHistoryAccountBlockPrefix) + DeleteHistoryByRange(db, StateHistoryStorageBlockPrefix) +} + +// DeleteTrienodeHistoryIndexes completely removes all trienode history indexing data. +func DeleteTrienodeHistoryIndexes(db ethdb.KeyValueRangeDeleter) { + DeleteHistoryByRange(db, TrienodeHistoryMetadataPrefix) + DeleteHistoryByRange(db, TrienodeHistoryBlockPrefix) +} + +// DeleteHistoryByRange completely removes all database entries with the specific prefix. +// Note, this method assumes the space with the given prefix is exclusively occupied! +func DeleteHistoryByRange(db ethdb.KeyValueRangeDeleter, prefix []byte) { + start := prefix + limit := increaseKey(bytes.Clone(prefix)) // Try to remove the data in the range by a loop, as the leveldb // doesn't support the native range deletion. diff --git a/core/rawdb/accessors_state.go b/core/rawdb/accessors_state.go index 46aa5fd070..298ad04f40 100644 --- a/core/rawdb/accessors_state.go +++ b/core/rawdb/accessors_state.go @@ -299,3 +299,76 @@ func WriteStateHistory(db ethdb.AncientWriter, id uint64, meta []byte, accountIn }) return err } + +// ReadTrienodeHistory retrieves the trienode history corresponding to the specified id. +// Compute the position of trienode history in freezer by minus one since the id of first +// trienode history starts from one(zero for initial state). +func ReadTrienodeHistory(db ethdb.AncientReaderOp, id uint64) ([]byte, []byte, []byte, error) { + header, err := db.Ancient(trienodeHistoryHeaderTable, id-1) + if err != nil { + return nil, nil, nil, err + } + keySection, err := db.Ancient(trienodeHistoryKeySectionTable, id-1) + if err != nil { + return nil, nil, nil, err + } + valueSection, err := db.Ancient(trienodeHistoryValueSectionTable, id-1) + if err != nil { + return nil, nil, nil, err + } + return header, keySection, valueSection, nil +} + +// ReadTrienodeHistoryHeader retrieves the header section of trienode history. +func ReadTrienodeHistoryHeader(db ethdb.AncientReaderOp, id uint64) ([]byte, error) { + return db.Ancient(trienodeHistoryHeaderTable, id-1) +} + +// ReadTrienodeHistoryKeySection retrieves the key section of trienode history. +func ReadTrienodeHistoryKeySection(db ethdb.AncientReaderOp, id uint64) ([]byte, error) { + return db.Ancient(trienodeHistoryKeySectionTable, id-1) +} + +// ReadTrienodeHistoryValueSection retrieves the value section of trienode history. +func ReadTrienodeHistoryValueSection(db ethdb.AncientReaderOp, id uint64) ([]byte, error) { + return db.Ancient(trienodeHistoryValueSectionTable, id-1) +} + +// ReadTrienodeHistoryList retrieves the a list of trienode history corresponding +// to the specified range. +// Compute the position of trienode history in freezer by minus one since the id +// of first trienode history starts from one(zero for initial state). +func ReadTrienodeHistoryList(db ethdb.AncientReaderOp, start uint64, count uint64) ([][]byte, [][]byte, [][]byte, error) { + header, err := db.AncientRange(trienodeHistoryHeaderTable, start-1, count, 0) + if err != nil { + return nil, nil, nil, err + } + keySection, err := db.AncientRange(trienodeHistoryKeySectionTable, start-1, count, 0) + if err != nil { + return nil, nil, nil, err + } + valueSection, err := db.AncientRange(trienodeHistoryValueSectionTable, start-1, count, 0) + if err != nil { + return nil, nil, nil, err + } + if len(header) != len(keySection) || len(header) != len(valueSection) { + return nil, nil, nil, errors.New("trienode history is corrupted") + } + return header, keySection, valueSection, nil +} + +// WriteTrienodeHistory writes the provided trienode history to database. +// Compute the position of trienode history in freezer by minus one since +// the id of first state history starts from one(zero for initial state). +func WriteTrienodeHistory(db ethdb.AncientWriter, id uint64, header []byte, keySection []byte, valueSection []byte) error { + _, err := db.ModifyAncients(func(op ethdb.AncientWriteOp) error { + if err := op.AppendRaw(trienodeHistoryHeaderTable, id-1, header); err != nil { + return err + } + if err := op.AppendRaw(trienodeHistoryKeySectionTable, id-1, keySection); err != nil { + return err + } + return op.AppendRaw(trienodeHistoryValueSectionTable, id-1, valueSection) + }) + return err +} diff --git a/core/rawdb/ancient_scheme.go b/core/rawdb/ancient_scheme.go index 1ffebed3e7..afec7848c8 100644 --- a/core/rawdb/ancient_scheme.go +++ b/core/rawdb/ancient_scheme.go @@ -75,15 +75,38 @@ var stateFreezerTableConfigs = map[string]freezerTableConfig{ stateHistoryStorageData: {noSnappy: false, prunable: true}, } +const ( + trienodeHistoryHeaderTable = "trienode.header" + trienodeHistoryKeySectionTable = "trienode.key" + trienodeHistoryValueSectionTable = "trienode.value" +) + +// trienodeFreezerTableConfigs configures the settings for tables in the trienode freezer. +var trienodeFreezerTableConfigs = map[string]freezerTableConfig{ + trienodeHistoryHeaderTable: {noSnappy: false, prunable: true}, + + // Disable snappy compression to allow efficient partial read. + trienodeHistoryKeySectionTable: {noSnappy: true, prunable: true}, + + // Disable snappy compression to allow efficient partial read. + trienodeHistoryValueSectionTable: {noSnappy: true, prunable: true}, +} + // The list of identifiers of ancient stores. var ( - ChainFreezerName = "chain" // the folder name of chain segment ancient store. - MerkleStateFreezerName = "state" // the folder name of state history ancient store. - VerkleStateFreezerName = "state_verkle" // the folder name of state history ancient store. + ChainFreezerName = "chain" // the folder name of chain segment ancient store. + MerkleStateFreezerName = "state" // the folder name of state history ancient store. + VerkleStateFreezerName = "state_verkle" // the folder name of state history ancient store. + MerkleTrienodeFreezerName = "trienode" // the folder name of trienode history ancient store. + VerkleTrienodeFreezerName = "trienode_verkle" // the folder name of trienode history ancient store. ) // freezers the collections of all builtin freezers. -var freezers = []string{ChainFreezerName, MerkleStateFreezerName, VerkleStateFreezerName} +var freezers = []string{ + ChainFreezerName, + MerkleStateFreezerName, VerkleStateFreezerName, + MerkleTrienodeFreezerName, VerkleTrienodeFreezerName, +} // NewStateFreezer initializes the ancient store for state history. // @@ -103,3 +126,22 @@ func NewStateFreezer(ancientDir string, verkle bool, readOnly bool) (ethdb.Reset } return newResettableFreezer(name, "eth/db/state", readOnly, stateHistoryTableSize, stateFreezerTableConfigs) } + +// NewTrienodeFreezer initializes the ancient store for trienode history. +// +// - if the empty directory is given, initializes the pure in-memory +// trienode freezer (e.g. dev mode). +// - if non-empty directory is given, initializes the regular file-based +// trienode freezer. +func NewTrienodeFreezer(ancientDir string, verkle bool, readOnly bool) (ethdb.ResettableAncientStore, error) { + if ancientDir == "" { + return NewMemoryFreezer(readOnly, trienodeFreezerTableConfigs), nil + } + var name string + if verkle { + name = filepath.Join(ancientDir, VerkleTrienodeFreezerName) + } else { + name = filepath.Join(ancientDir, MerkleTrienodeFreezerName) + } + return newResettableFreezer(name, "eth/db/trienode", readOnly, stateHistoryTableSize, trienodeFreezerTableConfigs) +} diff --git a/core/rawdb/schema.go b/core/rawdb/schema.go index 9a17e1c173..d9140c5fd6 100644 --- a/core/rawdb/schema.go +++ b/core/rawdb/schema.go @@ -80,6 +80,10 @@ var ( // been indexed. headStateHistoryIndexKey = []byte("LastStateHistoryIndex") + // headTrienodeHistoryIndexKey tracks the ID of the latest state history that has + // been indexed. + headTrienodeHistoryIndexKey = []byte("LastTrienodeHistoryIndex") + // txIndexTailKey tracks the oldest block whose transactions have been indexed. txIndexTailKey = []byte("TransactionIndexTail") @@ -125,8 +129,10 @@ var ( StateHistoryIndexPrefix = []byte("m") // The global prefix of state history index data StateHistoryAccountMetadataPrefix = []byte("ma") // StateHistoryAccountMetadataPrefix + account address hash => account metadata StateHistoryStorageMetadataPrefix = []byte("ms") // StateHistoryStorageMetadataPrefix + account address hash + storage slot hash => slot metadata + TrienodeHistoryMetadataPrefix = []byte("mt") // TrienodeHistoryMetadataPrefix + account address hash + trienode path => trienode metadata StateHistoryAccountBlockPrefix = []byte("mba") // StateHistoryAccountBlockPrefix + account address hash + blockID => account block StateHistoryStorageBlockPrefix = []byte("mbs") // StateHistoryStorageBlockPrefix + account address hash + storage slot hash + blockID => slot block + TrienodeHistoryBlockPrefix = []byte("mbt") // TrienodeHistoryBlockPrefix + account address hash + trienode path + blockID => trienode block // VerklePrefix is the database prefix for Verkle trie data, which includes: // (a) Trie nodes @@ -395,27 +401,34 @@ func storageHistoryIndexKey(addressHash common.Hash, storageHash common.Hash) [] return out } +// trienodeHistoryIndexKey = TrienodeHistoryMetadataPrefix + addressHash + trienode path +func trienodeHistoryIndexKey(addressHash common.Hash, path []byte) []byte { + totalLen := len(TrienodeHistoryMetadataPrefix) + common.HashLength + len(path) + out := make([]byte, totalLen) + + off := 0 + off += copy(out[off:], TrienodeHistoryMetadataPrefix) + off += copy(out[off:], addressHash.Bytes()) + copy(out[off:], path) + + return out +} + // accountHistoryIndexBlockKey = StateHistoryAccountBlockPrefix + addressHash + blockID func accountHistoryIndexBlockKey(addressHash common.Hash, blockID uint32) []byte { - var buf4 [4]byte - binary.BigEndian.PutUint32(buf4[:], blockID) - totalLen := len(StateHistoryAccountBlockPrefix) + common.HashLength + 4 out := make([]byte, totalLen) off := 0 off += copy(out[off:], StateHistoryAccountBlockPrefix) off += copy(out[off:], addressHash.Bytes()) - copy(out[off:], buf4[:]) + binary.BigEndian.PutUint32(out[off:], blockID) return out } // storageHistoryIndexBlockKey = StateHistoryStorageBlockPrefix + addressHash + storageHash + blockID func storageHistoryIndexBlockKey(addressHash common.Hash, storageHash common.Hash, blockID uint32) []byte { - var buf4 [4]byte - binary.BigEndian.PutUint32(buf4[:], blockID) - totalLen := len(StateHistoryStorageBlockPrefix) + 2*common.HashLength + 4 out := make([]byte, totalLen) @@ -423,7 +436,21 @@ func storageHistoryIndexBlockKey(addressHash common.Hash, storageHash common.Has off += copy(out[off:], StateHistoryStorageBlockPrefix) off += copy(out[off:], addressHash.Bytes()) off += copy(out[off:], storageHash.Bytes()) - copy(out[off:], buf4[:]) + binary.BigEndian.PutUint32(out[off:], blockID) + + return out +} + +// trienodeHistoryIndexBlockKey = TrienodeHistoryBlockPrefix + addressHash + trienode path + blockID +func trienodeHistoryIndexBlockKey(addressHash common.Hash, path []byte, blockID uint32) []byte { + totalLen := len(TrienodeHistoryBlockPrefix) + common.HashLength + len(path) + 4 + out := make([]byte, totalLen) + + off := 0 + off += copy(out[off:], TrienodeHistoryBlockPrefix) + off += copy(out[off:], addressHash.Bytes()) + off += copy(out[off:], path) + binary.BigEndian.PutUint32(out[off:], blockID) return out } diff --git a/triedb/pathdb/database.go b/triedb/pathdb/database.go index 546d2e0301..9fc65de277 100644 --- a/triedb/pathdb/database.go +++ b/triedb/pathdb/database.go @@ -232,7 +232,7 @@ func (db *Database) repairHistory() error { // Purge all state history indexing data first batch := db.diskdb.NewBatch() rawdb.DeleteStateHistoryIndexMetadata(batch) - rawdb.DeleteStateHistoryIndex(batch) + rawdb.DeleteStateHistoryIndexes(batch) if err := batch.Write(); err != nil { log.Crit("Failed to purge state history index", "err", err) } @@ -426,7 +426,7 @@ func (db *Database) Enable(root common.Hash) error { // Purge all state history indexing data first batch.Reset() rawdb.DeleteStateHistoryIndexMetadata(batch) - rawdb.DeleteStateHistoryIndex(batch) + rawdb.DeleteStateHistoryIndexes(batch) if err := batch.Write(); err != nil { return err } diff --git a/triedb/pathdb/history.go b/triedb/pathdb/history.go index 81b843d9f1..d78999f218 100644 --- a/triedb/pathdb/history.go +++ b/triedb/pathdb/history.go @@ -32,6 +32,9 @@ type historyType uint8 const ( // typeStateHistory indicates history data related to account or storage changes. typeStateHistory historyType = 0 + + // typeTrienodeHistory indicates history data related to trie node changes. + typeTrienodeHistory historyType = 1 ) // String returns the string format representation. @@ -39,6 +42,8 @@ func (h historyType) String() string { switch h { case typeStateHistory: return "state" + case typeTrienodeHistory: + return "trienode" default: return fmt.Sprintf("unknown type: %d", h) } @@ -48,8 +53,9 @@ func (h historyType) String() string { type elementType uint8 const ( - typeAccount elementType = 0 // represents the account data - typeStorage elementType = 1 // represents the storage slot data + typeAccount elementType = 0 // represents the account data + typeStorage elementType = 1 // represents the storage slot data + typeTrienode elementType = 2 // represents the trie node data ) // String returns the string format representation. @@ -59,6 +65,8 @@ func (e elementType) String() string { return "account" case typeStorage: return "storage" + case typeTrienode: + return "trienode" default: return fmt.Sprintf("unknown element type: %d", e) } @@ -69,11 +77,14 @@ func toHistoryType(typ elementType) historyType { if typ == typeAccount || typ == typeStorage { return typeStateHistory } + if typ == typeTrienode { + return typeTrienodeHistory + } panic(fmt.Sprintf("unknown element type %v", typ)) } // stateIdent represents the identifier of a state element, which can be -// an account or a storage slot. +// an account, a storage slot or a trienode. type stateIdent struct { typ elementType @@ -91,6 +102,12 @@ type stateIdent struct { // // This field is null if the identifier refers to an account or a trie node. storageHash common.Hash + + // The trie node path within the trie. + // + // This field is null if the identifier refers to an account or a storage slot. + // String type is chosen to make stateIdent comparable. + path string } // String returns the string format state identifier. @@ -98,7 +115,10 @@ func (ident stateIdent) String() string { if ident.typ == typeAccount { return ident.addressHash.Hex() } - return ident.addressHash.Hex() + ident.storageHash.Hex() + if ident.typ == typeStorage { + return ident.addressHash.Hex() + ident.storageHash.Hex() + } + return ident.addressHash.Hex() + ident.path } // newAccountIdent constructs a state identifier for an account. @@ -120,8 +140,18 @@ func newStorageIdent(addressHash common.Hash, storageHash common.Hash) stateIden } } -// stateIdentQuery is the extension of stateIdent by adding the account address -// and raw storage key. +// newTrienodeIdent constructs a state identifier for a trie node. +// The address denotes the address hash of the associated account; +// the path denotes the path of the node within the trie; +func newTrienodeIdent(addressHash common.Hash, path string) stateIdent { + return stateIdent{ + typ: typeTrienode, + addressHash: addressHash, + path: path, + } +} + +// stateIdentQuery is the extension of stateIdent by adding the raw storage key. type stateIdentQuery struct { stateIdent @@ -150,8 +180,19 @@ func newStorageIdentQuery(address common.Address, addressHash common.Hash, stora } } -// history defines the interface of historical data, implemented by stateHistory -// and trienodeHistory (in the near future). +// newTrienodeIdentQuery constructs a state identifier for a trie node. +// the addressHash denotes the address hash of the associated account; +// the path denotes the path of the node within the trie; +// +// nolint:unused +func newTrienodeIdentQuery(addrHash common.Hash, path []byte) stateIdentQuery { + return stateIdentQuery{ + stateIdent: newTrienodeIdent(addrHash, string(path)), + } +} + +// history defines the interface of historical data, shared by stateHistory +// and trienodeHistory. type history interface { // typ returns the historical data type held in the history. typ() historyType diff --git a/triedb/pathdb/history_index.go b/triedb/pathdb/history_index.go index 47cee9820d..5b4c91d7e6 100644 --- a/triedb/pathdb/history_index.go +++ b/triedb/pathdb/history_index.go @@ -376,6 +376,8 @@ func readStateIndex(ident stateIdent, db ethdb.KeyValueReader) []byte { return rawdb.ReadAccountHistoryIndex(db, ident.addressHash) case typeStorage: return rawdb.ReadStorageHistoryIndex(db, ident.addressHash, ident.storageHash) + case typeTrienode: + return rawdb.ReadTrienodeHistoryIndex(db, ident.addressHash, []byte(ident.path)) default: panic(fmt.Errorf("unknown type: %v", ident.typ)) } @@ -389,6 +391,8 @@ func writeStateIndex(ident stateIdent, db ethdb.KeyValueWriter, data []byte) { rawdb.WriteAccountHistoryIndex(db, ident.addressHash, data) case typeStorage: rawdb.WriteStorageHistoryIndex(db, ident.addressHash, ident.storageHash, data) + case typeTrienode: + rawdb.WriteTrienodeHistoryIndex(db, ident.addressHash, []byte(ident.path), data) default: panic(fmt.Errorf("unknown type: %v", ident.typ)) } @@ -402,6 +406,8 @@ func deleteStateIndex(ident stateIdent, db ethdb.KeyValueWriter) { rawdb.DeleteAccountHistoryIndex(db, ident.addressHash) case typeStorage: rawdb.DeleteStorageHistoryIndex(db, ident.addressHash, ident.storageHash) + case typeTrienode: + rawdb.DeleteTrienodeHistoryIndex(db, ident.addressHash, []byte(ident.path)) default: panic(fmt.Errorf("unknown type: %v", ident.typ)) } @@ -415,6 +421,8 @@ func readStateIndexBlock(ident stateIdent, db ethdb.KeyValueReader, id uint32) [ return rawdb.ReadAccountHistoryIndexBlock(db, ident.addressHash, id) case typeStorage: return rawdb.ReadStorageHistoryIndexBlock(db, ident.addressHash, ident.storageHash, id) + case typeTrienode: + return rawdb.ReadTrienodeHistoryIndexBlock(db, ident.addressHash, []byte(ident.path), id) default: panic(fmt.Errorf("unknown type: %v", ident.typ)) } @@ -428,6 +436,8 @@ func writeStateIndexBlock(ident stateIdent, db ethdb.KeyValueWriter, id uint32, rawdb.WriteAccountHistoryIndexBlock(db, ident.addressHash, id, data) case typeStorage: rawdb.WriteStorageHistoryIndexBlock(db, ident.addressHash, ident.storageHash, id, data) + case typeTrienode: + rawdb.WriteTrienodeHistoryIndexBlock(db, ident.addressHash, []byte(ident.path), id, data) default: panic(fmt.Errorf("unknown type: %v", ident.typ)) } @@ -441,6 +451,8 @@ func deleteStateIndexBlock(ident stateIdent, db ethdb.KeyValueWriter, id uint32) rawdb.DeleteAccountHistoryIndexBlock(db, ident.addressHash, id) case typeStorage: rawdb.DeleteStorageHistoryIndexBlock(db, ident.addressHash, ident.storageHash, id) + case typeTrienode: + rawdb.DeleteTrienodeHistoryIndexBlock(db, ident.addressHash, []byte(ident.path), id) default: panic(fmt.Errorf("unknown type: %v", ident.typ)) } diff --git a/triedb/pathdb/history_indexer.go b/triedb/pathdb/history_indexer.go index d618585929..368ff78d41 100644 --- a/triedb/pathdb/history_indexer.go +++ b/triedb/pathdb/history_indexer.go @@ -36,8 +36,10 @@ const ( // The batch size for reading state histories historyReadBatch = 1000 - stateIndexV0 = uint8(0) // initial version of state index structure - stateIndexVersion = stateIndexV0 // the current state index version + stateHistoryIndexV0 = uint8(0) // initial version of state index structure + stateHistoryIndexVersion = stateHistoryIndexV0 // the current state index version + trienodeHistoryIndexV0 = uint8(0) // initial version of trienode index structure + trienodeHistoryIndexVersion = trienodeHistoryIndexV0 // the current trienode index version ) // indexVersion returns the latest index version for the given history type. @@ -45,7 +47,9 @@ const ( func indexVersion(typ historyType) uint8 { switch typ { case typeStateHistory: - return stateIndexVersion + return stateHistoryIndexVersion + case typeTrienodeHistory: + return trienodeHistoryIndexVersion default: panic(fmt.Errorf("unknown history type: %d", typ)) } @@ -63,6 +67,8 @@ func loadIndexMetadata(db ethdb.KeyValueReader, typ historyType) *indexMetadata switch typ { case typeStateHistory: blob = rawdb.ReadStateHistoryIndexMetadata(db) + case typeTrienodeHistory: + blob = rawdb.ReadTrienodeHistoryIndexMetadata(db) default: panic(fmt.Errorf("unknown history type %d", typ)) } @@ -90,6 +96,8 @@ func storeIndexMetadata(db ethdb.KeyValueWriter, typ historyType, last uint64) { switch typ { case typeStateHistory: rawdb.WriteStateHistoryIndexMetadata(db, blob) + case typeTrienodeHistory: + rawdb.WriteTrienodeHistoryIndexMetadata(db, blob) default: panic(fmt.Errorf("unknown history type %d", typ)) } @@ -101,6 +109,8 @@ func deleteIndexMetadata(db ethdb.KeyValueWriter, typ historyType) { switch typ { case typeStateHistory: rawdb.DeleteStateHistoryIndexMetadata(db) + case typeTrienodeHistory: + rawdb.DeleteTrienodeHistoryIndexMetadata(db) default: panic(fmt.Errorf("unknown history type %d", typ)) } @@ -215,7 +225,11 @@ func (b *batchIndexer) finish(force bool) error { func indexSingle(historyID uint64, db ethdb.KeyValueStore, freezer ethdb.AncientReader, typ historyType) error { start := time.Now() defer func() { - indexHistoryTimer.UpdateSince(start) + if typ == typeStateHistory { + stateIndexHistoryTimer.UpdateSince(start) + } else if typ == typeTrienodeHistory { + trienodeIndexHistoryTimer.UpdateSince(start) + } }() metadata := loadIndexMetadata(db, typ) @@ -234,7 +248,7 @@ func indexSingle(historyID uint64, db ethdb.KeyValueStore, freezer ethdb.Ancient if typ == typeStateHistory { h, err = readStateHistory(freezer, historyID) } else { - // h, err = readTrienodeHistory(freezer, historyID) + h, err = readTrienodeHistory(freezer, historyID) } if err != nil { return err @@ -253,7 +267,11 @@ func indexSingle(historyID uint64, db ethdb.KeyValueStore, freezer ethdb.Ancient func unindexSingle(historyID uint64, db ethdb.KeyValueStore, freezer ethdb.AncientReader, typ historyType) error { start := time.Now() defer func() { - unindexHistoryTimer.UpdateSince(start) + if typ == typeStateHistory { + stateUnindexHistoryTimer.UpdateSince(start) + } else if typ == typeTrienodeHistory { + trienodeUnindexHistoryTimer.UpdateSince(start) + } }() metadata := loadIndexMetadata(db, typ) @@ -272,7 +290,7 @@ func unindexSingle(historyID uint64, db ethdb.KeyValueStore, freezer ethdb.Ancie if typ == typeStateHistory { h, err = readStateHistory(freezer, historyID) } else { - // h, err = readTrienodeHistory(freezer, historyID) + h, err = readTrienodeHistory(freezer, historyID) } if err != nil { return err @@ -546,13 +564,13 @@ func (i *indexIniter) index(done chan struct{}, interrupt *atomic.Int32, lastID return } } else { - // histories, err = readTrienodeHistories(i.freezer, current, count) - // if err != nil { - // // The history read might fall if the history is truncated from - // // head due to revert operation. - // i.log.Error("Failed to read history for indexing", "current", current, "count", count, "err", err) - // return - // } + histories, err = readTrienodeHistories(i.freezer, current, count) + if err != nil { + // The history read might fall if the history is truncated from + // head due to revert operation. + i.log.Error("Failed to read history for indexing", "current", current, "count", count, "err", err) + return + } } for _, h := range histories { if err := batch.process(h, current); err != nil { @@ -570,7 +588,7 @@ func (i *indexIniter) index(done chan struct{}, interrupt *atomic.Int32, lastID done = current - beginID ) eta := common.CalculateETA(done, left, time.Since(start)) - i.log.Info("Indexing state history", "processed", done, "left", left, "elapsed", common.PrettyDuration(time.Since(start)), "eta", common.PrettyDuration(eta)) + i.log.Info("Indexing history", "processed", done, "left", left, "elapsed", common.PrettyDuration(time.Since(start)), "eta", common.PrettyDuration(eta)) } } i.indexed.Store(current - 1) // update indexing progress @@ -657,6 +675,8 @@ func checkVersion(disk ethdb.KeyValueStore, typ historyType) { var blob []byte if typ == typeStateHistory { blob = rawdb.ReadStateHistoryIndexMetadata(disk) + } else if typ == typeTrienodeHistory { + blob = rawdb.ReadTrienodeHistoryIndexMetadata(disk) } else { panic(fmt.Errorf("unknown history type: %v", typ)) } @@ -666,24 +686,32 @@ func checkVersion(disk ethdb.KeyValueStore, typ historyType) { return } // Short circuit if the metadata is found and the version is matched + ver := stateHistoryIndexVersion + if typ == typeTrienodeHistory { + ver = trienodeHistoryIndexVersion + } var m indexMetadata err := rlp.DecodeBytes(blob, &m) - if err == nil && m.Version == stateIndexVersion { + if err == nil && m.Version == ver { return } // Version is not matched, prune the existing data and re-index from scratch + batch := disk.NewBatch() + if typ == typeStateHistory { + rawdb.DeleteStateHistoryIndexMetadata(batch) + rawdb.DeleteStateHistoryIndexes(batch) + } else { + rawdb.DeleteTrienodeHistoryIndexMetadata(batch) + rawdb.DeleteTrienodeHistoryIndexes(batch) + } + if err := batch.Write(); err != nil { + log.Crit("Failed to purge history index", "type", typ, "err", err) + } version := "unknown" if err == nil { version = fmt.Sprintf("%d", m.Version) } - - batch := disk.NewBatch() - rawdb.DeleteStateHistoryIndexMetadata(batch) - rawdb.DeleteStateHistoryIndex(batch) - if err := batch.Write(); err != nil { - log.Crit("Failed to purge state history index", "err", err) - } - log.Info("Cleaned up obsolete state history index", "version", version, "want", stateIndexVersion) + log.Info("Cleaned up obsolete history index", "type", typ, "version", version, "want", version) } // newHistoryIndexer constructs the history indexer and launches the background diff --git a/triedb/pathdb/history_state.go b/triedb/pathdb/history_state.go index 9d1e4dfb09..bc21915dba 100644 --- a/triedb/pathdb/history_state.go +++ b/triedb/pathdb/history_state.go @@ -605,9 +605,9 @@ func writeStateHistory(writer ethdb.AncientWriter, dl *diffLayer) error { if err := rawdb.WriteStateHistory(writer, dl.stateID(), history.meta.encode(), accountIndex, storageIndex, accountData, storageData); err != nil { return err } - historyDataBytesMeter.Mark(int64(dataSize)) - historyIndexBytesMeter.Mark(int64(indexSize)) - historyBuildTimeMeter.UpdateSince(start) + stateHistoryDataBytesMeter.Mark(int64(dataSize)) + stateHistoryIndexBytesMeter.Mark(int64(indexSize)) + stateHistoryBuildTimeMeter.UpdateSince(start) log.Debug("Stored state history", "id", dl.stateID(), "block", dl.block, "data", dataSize, "index", indexSize, "elapsed", common.PrettyDuration(time.Since(start))) return nil diff --git a/triedb/pathdb/history_state_test.go b/triedb/pathdb/history_state_test.go index 5718081566..4046fb9640 100644 --- a/triedb/pathdb/history_state_test.go +++ b/triedb/pathdb/history_state_test.go @@ -98,13 +98,13 @@ func testEncodeDecodeStateHistory(t *testing.T, rawStorageKey bool) { if !compareSet(dec.accounts, obj.accounts) { t.Fatal("account data is mismatched") } - if !compareStorages(dec.storages, obj.storages) { + if !compareMapSet(dec.storages, obj.storages) { t.Fatal("storage data is mismatched") } if !compareList(dec.accountList, obj.accountList) { t.Fatal("account list is mismatched") } - if !compareStorageList(dec.storageList, obj.storageList) { + if !compareMapList(dec.storageList, obj.storageList) { t.Fatal("storage list is mismatched") } } @@ -292,32 +292,32 @@ func compareList[k comparable](a, b []k) bool { return true } -func compareStorages(a, b map[common.Address]map[common.Hash][]byte) bool { +func compareMapSet[K1 comparable, K2 comparable](a, b map[K1]map[K2][]byte) bool { if len(a) != len(b) { return false } - for h, subA := range a { - subB, ok := b[h] + for key, subsetA := range a { + subsetB, ok := b[key] if !ok { return false } - if !compareSet(subA, subB) { + if !compareSet(subsetA, subsetB) { return false } } return true } -func compareStorageList(a, b map[common.Address][]common.Hash) bool { +func compareMapList[K comparable, V comparable](a, b map[K][]V) bool { if len(a) != len(b) { return false } - for h, la := range a { - lb, ok := b[h] + for key, listA := range a { + listB, ok := b[key] if !ok { return false } - if !compareList(la, lb) { + if !compareList(listA, listB) { return false } } diff --git a/triedb/pathdb/history_trienode.go b/triedb/pathdb/history_trienode.go new file mode 100644 index 0000000000..2a4459d4ad --- /dev/null +++ b/triedb/pathdb/history_trienode.go @@ -0,0 +1,730 @@ +// 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 ( + "bytes" + "encoding/binary" + "fmt" + "iter" + "maps" + "math" + "slices" + "sort" + "time" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/rawdb" + "github.com/ethereum/go-ethereum/ethdb" + "github.com/ethereum/go-ethereum/log" +) + +// Each trie node history entry consists of three parts (stored in three freezer +// tables according): +// +// # Header +// The header records metadata, including: +// +// - the history version (1 byte) +// - the parent state root (32 bytes) +// - the current state root (32 bytes) +// - block number (8 bytes) +// +// - a lexicographically sorted list of trie IDs +// - the corresponding offsets into the key and value sections for each trie data chunk +// +// Although some fields (e.g., parent state root, block number) are duplicated +// between the state history and the trienode history, these two histories +// operate independently. To ensure each remains self-contained and self-descriptive, +// we have chosen to maintain these duplicate fields. +// +// # Key section +// The key section stores trie node keys (paths) in a compressed format. +// It also contains relative offsets into the value section for resolving +// the corresponding trie node data. Note that these offsets are relative +// to the data chunk for the trie; the chunk offset must be added to obtain +// the absolute position. +// +// # Value section +// The value section is a concatenated byte stream of all trie node data. +// Each trie node can be retrieved using the offset and length specified +// by its index entry. +// +// The header and key sections are sufficient for locating a trie node, +// while a partial read of the value section is enough to retrieve its data. + +// Header section: +// +// +----------+------------------+---------------------+---------------------+-------+------------------+---------------------+---------------------| +// | metadata | TrieID(32 bytes) | key offset(4 bytes) | val offset(4 bytes) | ... | TrieID(32 bytes) | key offset(4 bytes) | val offset(4 bytes) | +// +----------+------------------+---------------------+---------------------+-------+------------------+---------------------+---------------------| +// +// +// Key section: +// +// + restart point + restart point (depends on restart interval) +// / / +// +---------------+---------------+---------------+---------------+---------+ +// | node entry 1 | node entry 2 | ... | node entry n | trailer | +// +---------------+---------------+---------------+---------------+---------+ +// \ / +// +---- restart block ------+ +// +// node entry: +// +// +---- key len ----+ +// / \ +// +-------+---------+-----------+---------+-----------------------+-----------------+ +// | shared (varint) | not shared (varint) | value length (varlen) | key (varlen) | +// +-----------------+---------------------+-----------------------+-----------------+ +// +// trailer: +// +// +---- 4-bytes ----+ +---- 4-bytes ----+ +// / \ / \ +// +----------------------+------------------------+-----+--------------------------+ +// | restart_1 key offset | restart_1 value offset | ... | restart number (4-bytes) | +// +----------------------+------------------------+-----+--------------------------+ +// +// Note: Both the key offset and the value offset are relative to the start of +// the trie data chunk. To obtain the absolute offset, add the offset of the +// trie data chunk itself. +// +// Value section: +// +// +--------------+--------------+-------+---------------+ +// | node data 1 | node data 2 | ... | node data n | +// +--------------+--------------+-------+---------------+ +// +// NOTE: All fixed-length integer are big-endian. + +const ( + trienodeHistoryV0 = uint8(0) // initial version of node history structure + trienodeHistoryVersion = trienodeHistoryV0 // the default node history version + trienodeMetadataSize = 1 + 2*common.HashLength + 8 // the size of metadata in the history + trienodeTrieHeaderSize = 8 + common.HashLength // the size of a single trie header in history + trienodeDataBlockRestartLen = 16 // The restart interval length of trie node block +) + +// trienodeMetadata describes the meta data of trienode history. +type trienodeMetadata struct { + version uint8 // version tag of history object + parent common.Hash // prev-state root before the state transition + root common.Hash // post-state root after the state transition + block uint64 // associated block number +} + +// trienodeHistory represents a set of trie node changes resulting from a state +// transition across the main account trie and all associated storage tries. +type trienodeHistory struct { + meta *trienodeMetadata // Metadata of the history + owners []common.Hash // List of trie identifier sorted lexicographically + nodeList map[common.Hash][]string // Set of node paths sorted lexicographically + nodes map[common.Hash]map[string][]byte // Set of original value of trie nodes before state transition +} + +// newTrienodeHistory constructs a trienode history with the provided trie nodes. +func newTrienodeHistory(root common.Hash, parent common.Hash, block uint64, nodes map[common.Hash]map[string][]byte) *trienodeHistory { + nodeList := make(map[common.Hash][]string) + for owner, subset := range nodes { + keys := sort.StringSlice(slices.Collect(maps.Keys(subset))) + keys.Sort() + nodeList[owner] = keys + } + return &trienodeHistory{ + meta: &trienodeMetadata{ + version: trienodeHistoryVersion, + parent: parent, + root: root, + block: block, + }, + owners: slices.SortedFunc(maps.Keys(nodes), common.Hash.Cmp), + nodeList: nodeList, + nodes: nodes, + } +} + +// sharedLen returns the length of the common prefix shared by a and b. +func sharedLen(a, b []byte) int { + n := min(len(a), len(b)) + for i := 0; i < n; i++ { + if a[i] != b[i] { + return i + } + } + return n +} + +// typ implements the history interface, returning the historical data type held. +func (h *trienodeHistory) typ() historyType { + return typeTrienodeHistory +} + +// forEach implements the history interface, returning an iterator to traverse the +// state entries in the history. +func (h *trienodeHistory) forEach() iter.Seq[stateIdent] { + return func(yield func(stateIdent) bool) { + for _, owner := range h.owners { + for _, path := range h.nodeList[owner] { + if !yield(newTrienodeIdent(owner, path)) { + return + } + } + } + } +} + +// encode serializes the contained trie nodes into bytes. +func (h *trienodeHistory) encode() ([]byte, []byte, []byte, error) { + var ( + buf = make([]byte, 64) + headerSection bytes.Buffer + keySection bytes.Buffer + valueSection bytes.Buffer + ) + binary.Write(&headerSection, binary.BigEndian, h.meta.version) // 1 byte + headerSection.Write(h.meta.parent.Bytes()) // 32 bytes + headerSection.Write(h.meta.root.Bytes()) // 32 bytes + binary.Write(&headerSection, binary.BigEndian, h.meta.block) // 8 byte + + for _, owner := range h.owners { + // Fill the header section with offsets at key and value section + headerSection.Write(owner.Bytes()) // 32 bytes + binary.Write(&headerSection, binary.BigEndian, uint32(keySection.Len())) // 4 bytes + + // The offset to the value section is theoretically unnecessary, since the + // individual value offset is already tracked in the key section. However, + // we still keep it here for two reasons: + // - It's cheap to store (only 4 bytes for each trie). + // - It can be useful for decoding the trie data when key is not required (e.g., in hash mode). + binary.Write(&headerSection, binary.BigEndian, uint32(valueSection.Len())) // 4 bytes + + // Fill the key section with node index + var ( + prevKey []byte + restarts []uint32 + prefixLen int + + internalKeyOffset uint32 // key offset for the trie internally + internalValOffset uint32 // value offset for the trie internally + ) + for i, path := range h.nodeList[owner] { + key := []byte(path) + if i%trienodeDataBlockRestartLen == 0 { + restarts = append(restarts, internalKeyOffset) + restarts = append(restarts, internalValOffset) + prefixLen = 0 + } else { + prefixLen = sharedLen(prevKey, key) + } + value := h.nodes[owner][path] + + // key section + n := binary.PutUvarint(buf[0:], uint64(prefixLen)) // key length shared (varint) + n += binary.PutUvarint(buf[n:], uint64(len(key)-prefixLen)) // key length not shared (varint) + n += binary.PutUvarint(buf[n:], uint64(len(value))) // value length (varint) + + if _, err := keySection.Write(buf[:n]); err != nil { + return nil, nil, nil, err + } + // unshared key + if _, err := keySection.Write(key[prefixLen:]); err != nil { + return nil, nil, nil, err + } + n += len(key) - prefixLen + prevKey = key + + // value section + if _, err := valueSection.Write(value); err != nil { + return nil, nil, nil, err + } + internalKeyOffset += uint32(n) + internalValOffset += uint32(len(value)) + } + + // Encode trailer, the number of restart sections is len(restarts))/2, + // as we track the offsets of both key and value sections. + var trailer []byte + for _, number := range append(restarts, uint32(len(restarts))/2) { + binary.BigEndian.PutUint32(buf[:4], number) + trailer = append(trailer, buf[:4]...) + } + if _, err := keySection.Write(trailer); err != nil { + return nil, nil, nil, err + } + } + return headerSection.Bytes(), keySection.Bytes(), valueSection.Bytes(), nil +} + +// decodeHeader resolves the metadata from the header section. An error +// should be returned if the header section is corrupted. +func decodeHeader(data []byte) (*trienodeMetadata, []common.Hash, []uint32, []uint32, error) { + if len(data) < trienodeMetadataSize { + return nil, nil, nil, nil, fmt.Errorf("trienode history is too small, index size: %d", len(data)) + } + version := data[0] + if version != trienodeHistoryVersion { + return nil, nil, nil, nil, fmt.Errorf("unregonized trienode history version: %d", version) + } + parent := common.BytesToHash(data[1 : common.HashLength+1]) // 32 bytes + root := common.BytesToHash(data[common.HashLength+1 : common.HashLength*2+1]) // 32 bytes + block := binary.BigEndian.Uint64(data[common.HashLength*2+1 : trienodeMetadataSize]) // 8 bytes + + size := len(data) - trienodeMetadataSize + if size%trienodeTrieHeaderSize != 0 { + return nil, nil, nil, nil, fmt.Errorf("truncated trienode history data, size %d", len(data)) + } + count := size / trienodeTrieHeaderSize + + var ( + owners = make([]common.Hash, 0, count) + keyOffsets = make([]uint32, 0, count) + valOffsets = make([]uint32, 0, count) + ) + for i := 0; i < count; i++ { + n := trienodeMetadataSize + trienodeTrieHeaderSize*i + owner := common.BytesToHash(data[n : n+common.HashLength]) + if i != 0 && bytes.Compare(owner.Bytes(), owners[i-1].Bytes()) <= 0 { + return nil, nil, nil, nil, fmt.Errorf("trienode owners are out of order, prev: %v, cur: %v", owners[i-1], owner) + } + owners = append(owners, owner) + + // Decode the offset to the key section + keyOffset := binary.BigEndian.Uint32(data[n+common.HashLength : n+common.HashLength+4]) + if i != 0 && keyOffset <= keyOffsets[i-1] { + return nil, nil, nil, nil, fmt.Errorf("key offset is out of order, prev: %v, cur: %v", keyOffsets[i-1], keyOffset) + } + keyOffsets = append(keyOffsets, keyOffset) + + // Decode the offset into the value section. Note that identical value offsets + // are valid if the node values in the last trie chunk are all zero (e.g., after + // a trie deletion). + valOffset := binary.BigEndian.Uint32(data[n+common.HashLength+4 : n+common.HashLength+8]) + if i != 0 && valOffset < valOffsets[i-1] { + return nil, nil, nil, nil, fmt.Errorf("value offset is out of order, prev: %v, cur: %v", valOffsets[i-1], valOffset) + } + valOffsets = append(valOffsets, valOffset) + } + return &trienodeMetadata{ + version: version, + parent: parent, + root: root, + block: block, + }, owners, keyOffsets, valOffsets, nil +} + +func decodeSingle(keySection []byte, onValue func([]byte, int, int) error) ([]string, error) { + var ( + prevKey []byte + items int + keyOffsets []uint32 + valOffsets []uint32 + + keyOff int // the key offset within the single trie data + valOff int // the value offset within the single trie data + + keys []string + ) + // Decode the number of restart section + if len(keySection) < 4 { + return nil, fmt.Errorf("key section too short, size: %d", len(keySection)) + } + nRestarts := binary.BigEndian.Uint32(keySection[len(keySection)-4:]) + + if len(keySection) < int(8*nRestarts)+4 { + return nil, fmt.Errorf("key section too short, restarts: %d, size: %d", nRestarts, len(keySection)) + } + for i := 0; i < int(nRestarts); i++ { + o := len(keySection) - 4 - (int(nRestarts)-i)*8 + keyOffset := binary.BigEndian.Uint32(keySection[o : o+4]) + if i != 0 && keyOffset <= keyOffsets[i-1] { + return nil, fmt.Errorf("key offset is out of order, prev: %v, cur: %v", keyOffsets[i-1], keyOffset) + } + keyOffsets = append(keyOffsets, keyOffset) + + // Same value offset is allowed just in case all the trie nodes in the last + // section have zero-size value. + valOffset := binary.BigEndian.Uint32(keySection[o+4 : o+8]) + if i != 0 && valOffset < valOffsets[i-1] { + return nil, fmt.Errorf("value offset is out of order, prev: %v, cur: %v", valOffsets[i-1], valOffset) + } + valOffsets = append(valOffsets, valOffset) + } + keyLimit := len(keySection) - 4 - int(nRestarts)*8 + + // Decode data + for keyOff < keyLimit { + // Validate the key and value offsets within the single trie data chunk + if items%trienodeDataBlockRestartLen == 0 { + if keyOff != int(keyOffsets[items/trienodeDataBlockRestartLen]) { + return nil, fmt.Errorf("key offset is not matched, recorded: %d, want: %d", keyOffsets[items/trienodeDataBlockRestartLen], keyOff) + } + if valOff != int(valOffsets[items/trienodeDataBlockRestartLen]) { + return nil, fmt.Errorf("value offset is not matched, recorded: %d, want: %d", valOffsets[items/trienodeDataBlockRestartLen], valOff) + } + } + // Resolve the entry from key section + nShared, nn := binary.Uvarint(keySection[keyOff:]) // key length shared (varint) + keyOff += nn + nUnshared, nn := binary.Uvarint(keySection[keyOff:]) // key length not shared (varint) + keyOff += nn + nValue, nn := binary.Uvarint(keySection[keyOff:]) // value length (varint) + keyOff += nn + + // Resolve unshared key + if keyOff+int(nUnshared) > len(keySection) { + return nil, fmt.Errorf("key length too long, unshared key length: %d, off: %d, section size: %d", nUnshared, keyOff, len(keySection)) + } + unsharedKey := keySection[keyOff : keyOff+int(nUnshared)] + keyOff += int(nUnshared) + + // Assemble the full key + var key []byte + if items%trienodeDataBlockRestartLen == 0 { + if nShared != 0 { + return nil, fmt.Errorf("unexpected non-zero shared key prefix: %d", nShared) + } + key = unsharedKey + } else { + if int(nShared) > len(prevKey) { + return nil, fmt.Errorf("unexpected shared key prefix: %d, prefix key length: %d", nShared, len(prevKey)) + } + key = append([]byte{}, prevKey[:nShared]...) + key = append(key, unsharedKey...) + } + if items != 0 && bytes.Compare(prevKey, key) >= 0 { + return nil, fmt.Errorf("trienode paths are out of order, prev: %v, cur: %v", prevKey, key) + } + prevKey = key + + // Resolve value + if onValue != nil { + if err := onValue(key, valOff, valOff+int(nValue)); err != nil { + return nil, err + } + } + valOff += int(nValue) + + items++ + keys = append(keys, string(key)) + } + if keyOff != keyLimit { + return nil, fmt.Errorf("excessive key data after decoding, offset: %d, size: %d", keyOff, keyLimit) + } + return keys, nil +} + +func decodeSingleWithValue(keySection []byte, valueSection []byte) ([]string, map[string][]byte, error) { + var ( + offset int + nodes = make(map[string][]byte) + ) + paths, err := decodeSingle(keySection, func(key []byte, start int, limit int) error { + if start != offset { + return fmt.Errorf("gapped value section offset: %d, want: %d", start, offset) + } + // start == limit is allowed for zero-value trie node (e.g., non-existent node) + if start > limit { + return fmt.Errorf("invalid value offsets, start: %d, limit: %d", start, limit) + } + if start > len(valueSection) || limit > len(valueSection) { + return fmt.Errorf("value section out of range: start: %d, limit: %d, size: %d", start, limit, len(valueSection)) + } + nodes[string(key)] = valueSection[start:limit] + + offset = limit + return nil + }) + if err != nil { + return nil, nil, err + } + if offset != len(valueSection) { + return nil, nil, fmt.Errorf("excessive value data after decoding, offset: %d, size: %d", offset, len(valueSection)) + } + return paths, nodes, nil +} + +// decode deserializes the contained trie nodes from the provided bytes. +func (h *trienodeHistory) decode(header []byte, keySection []byte, valueSection []byte) error { + metadata, owners, keyOffsets, valueOffsets, err := decodeHeader(header) + if err != nil { + return err + } + h.meta = metadata + h.owners = owners + h.nodeList = make(map[common.Hash][]string) + h.nodes = make(map[common.Hash]map[string][]byte) + + for i := 0; i < len(owners); i++ { + // Resolve the boundary of key section + keyStart := keyOffsets[i] + keyLimit := len(keySection) + if i != len(owners)-1 { + keyLimit = int(keyOffsets[i+1]) + } + if int(keyStart) > len(keySection) || keyLimit > len(keySection) { + return fmt.Errorf("invalid key offsets: keyStart: %d, keyLimit: %d, size: %d", keyStart, keyLimit, len(keySection)) + } + + // Resolve the boundary of value section + valStart := valueOffsets[i] + valLimit := len(valueSection) + if i != len(owners)-1 { + valLimit = int(valueOffsets[i+1]) + } + if int(valStart) > len(valueSection) || valLimit > len(valueSection) { + return fmt.Errorf("invalid value offsets: valueStart: %d, valueLimit: %d, size: %d", valStart, valLimit, len(valueSection)) + } + + // Decode the key and values for this specific trie + paths, nodes, err := decodeSingleWithValue(keySection[keyStart:keyLimit], valueSection[valStart:valLimit]) + if err != nil { + return err + } + h.nodeList[owners[i]] = paths + h.nodes[owners[i]] = nodes + } + return nil +} + +type iRange struct { + start uint32 + limit uint32 +} + +// singleTrienodeHistoryReader provides read access to a single trie within the +// trienode history. It stores an offset to the trie's position in the history, +// along with a set of per-node offsets that can be resolved on demand. +type singleTrienodeHistoryReader struct { + id uint64 + reader ethdb.AncientReader + valueRange iRange // value range within the total value section + valueInternalOffsets map[string]iRange // value offset within the single trie data +} + +func newSingleTrienodeHistoryReader(id uint64, reader ethdb.AncientReader, keyRange iRange, valueRange iRange) (*singleTrienodeHistoryReader, error) { + // TODO(rjl493456442) partial freezer read should be supported + keyData, err := rawdb.ReadTrienodeHistoryKeySection(reader, id) + if err != nil { + return nil, err + } + keyStart := int(keyRange.start) + keyLimit := int(keyRange.limit) + if keyLimit == math.MaxUint32 { + keyLimit = len(keyData) + } + if len(keyData) < keyStart || len(keyData) < keyLimit { + return nil, fmt.Errorf("key section too short, start: %d, limit: %d, size: %d", keyStart, keyLimit, len(keyData)) + } + + valueOffsets := make(map[string]iRange) + _, err = decodeSingle(keyData[keyStart:keyLimit], func(key []byte, start int, limit int) error { + valueOffsets[string(key)] = iRange{ + start: uint32(start), + limit: uint32(limit), + } + return nil + }) + if err != nil { + return nil, err + } + return &singleTrienodeHistoryReader{ + id: id, + reader: reader, + valueRange: valueRange, + valueInternalOffsets: valueOffsets, + }, nil +} + +// read retrieves the trie node data with the provided node path. +func (sr *singleTrienodeHistoryReader) read(path string) ([]byte, error) { + offset, exists := sr.valueInternalOffsets[path] + if !exists { + return nil, fmt.Errorf("trienode %v not found", []byte(path)) + } + // TODO(rjl493456442) partial freezer read should be supported + valueData, err := rawdb.ReadTrienodeHistoryValueSection(sr.reader, sr.id) + if err != nil { + return nil, err + } + if len(valueData) < int(sr.valueRange.start) { + return nil, fmt.Errorf("value section too short, start: %d, size: %d", sr.valueRange.start, len(valueData)) + } + entryStart := sr.valueRange.start + offset.start + entryLimit := sr.valueRange.start + offset.limit + if len(valueData) < int(entryStart) || len(valueData) < int(entryLimit) { + return nil, fmt.Errorf("value section too short, start: %d, limit: %d, size: %d", entryStart, entryLimit, len(valueData)) + } + return valueData[int(entryStart):int(entryLimit)], nil +} + +// trienodeHistoryReader provides read access to node data in the trie node history. +// It resolves data from the underlying ancient store only when needed, minimizing +// I/O overhead. +type trienodeHistoryReader struct { + id uint64 // ID of the associated trienode history + reader ethdb.AncientReader // Database reader of ancient store + keyRanges map[common.Hash]iRange // Key ranges identifying trie chunks + valRanges map[common.Hash]iRange // Value ranges identifying trie chunks + iReaders map[common.Hash]*singleTrienodeHistoryReader // readers for each individual trie chunk +} + +// newTrienodeHistoryReader constructs the reader for specific trienode history. +func newTrienodeHistoryReader(id uint64, reader ethdb.AncientReader) (*trienodeHistoryReader, error) { + r := &trienodeHistoryReader{ + id: id, + reader: reader, + keyRanges: make(map[common.Hash]iRange), + valRanges: make(map[common.Hash]iRange), + iReaders: make(map[common.Hash]*singleTrienodeHistoryReader), + } + if err := r.decodeHeader(); err != nil { + return nil, err + } + return r, nil +} + +// decodeHeader decodes the header section of trienode history. +func (r *trienodeHistoryReader) decodeHeader() error { + header, err := rawdb.ReadTrienodeHistoryHeader(r.reader, r.id) + if err != nil { + return err + } + _, owners, keyOffsets, valOffsets, err := decodeHeader(header) + if err != nil { + return err + } + for i, owner := range owners { + // Decode the key range for this trie chunk + var keyLimit uint32 + if i == len(owners)-1 { + keyLimit = math.MaxUint32 + } else { + keyLimit = keyOffsets[i+1] + } + r.keyRanges[owner] = iRange{ + start: keyOffsets[i], + limit: keyLimit, + } + + // Decode the value range for this trie chunk + var valLimit uint32 + if i == len(owners)-1 { + valLimit = math.MaxUint32 + } else { + valLimit = valOffsets[i+1] + } + r.valRanges[owner] = iRange{ + start: valOffsets[i], + limit: valLimit, + } + } + return nil +} + +// read retrieves the trie node data with the provided TrieID and node path. +func (r *trienodeHistoryReader) read(owner common.Hash, path string) ([]byte, error) { + ir, ok := r.iReaders[owner] + if !ok { + keyRange, exists := r.keyRanges[owner] + if !exists { + return nil, fmt.Errorf("trie %x is unknown", owner) + } + valRange, exists := r.valRanges[owner] + if !exists { + return nil, fmt.Errorf("trie %x is unknown", owner) + } + var err error + ir, err = newSingleTrienodeHistoryReader(r.id, r.reader, keyRange, valRange) + if err != nil { + return nil, err + } + r.iReaders[owner] = ir + } + return ir.read(path) +} + +// writeTrienodeHistory persists the trienode history associated with the given diff layer. +// nolint:unused +func writeTrienodeHistory(writer ethdb.AncientWriter, dl *diffLayer) error { + start := time.Now() + h := newTrienodeHistory(dl.rootHash(), dl.parent.rootHash(), dl.block, dl.nodes.nodeOrigin) + header, keySection, valueSection, err := h.encode() + if err != nil { + return err + } + // Write history data into five freezer table respectively. + if err := rawdb.WriteTrienodeHistory(writer, dl.stateID(), header, keySection, valueSection); err != nil { + return err + } + trienodeHistoryDataBytesMeter.Mark(int64(len(valueSection))) + trienodeHistoryIndexBytesMeter.Mark(int64(len(header) + len(keySection))) + trienodeHistoryBuildTimeMeter.UpdateSince(start) + + log.Debug( + "Stored trienode history", "id", dl.stateID(), "block", dl.block, + "header", common.StorageSize(len(header)), + "keySection", common.StorageSize(len(keySection)), + "valueSection", common.StorageSize(len(valueSection)), + "elapsed", common.PrettyDuration(time.Since(start)), + ) + return nil +} + +// readTrienodeMetadata resolves the metadata of the specified trienode history. +// nolint:unused +func readTrienodeMetadata(reader ethdb.AncientReader, id uint64) (*trienodeMetadata, error) { + header, err := rawdb.ReadTrienodeHistoryHeader(reader, id) + if err != nil { + return nil, err + } + metadata, _, _, _, err := decodeHeader(header) + if err != nil { + return nil, err + } + return metadata, nil +} + +// readTrienodeHistory resolves a single trienode history object with specific id. +func readTrienodeHistory(reader ethdb.AncientReader, id uint64) (*trienodeHistory, error) { + header, keySection, valueSection, err := rawdb.ReadTrienodeHistory(reader, id) + if err != nil { + return nil, err + } + var h trienodeHistory + if err := h.decode(header, keySection, valueSection); err != nil { + return nil, err + } + return &h, nil +} + +// readTrienodeHistories resolves a list of trienode histories with the specific range. +func readTrienodeHistories(reader ethdb.AncientReader, start uint64, count uint64) ([]history, error) { + headers, keySections, valueSections, err := rawdb.ReadTrienodeHistoryList(reader, start, count) + if err != nil { + return nil, err + } + var res []history + for i, header := range headers { + var h trienodeHistory + if err := h.decode(header, keySections[i], valueSections[i]); err != nil { + return nil, err + } + res = append(res, &h) + } + return res, nil +} diff --git a/triedb/pathdb/history_trienode_test.go b/triedb/pathdb/history_trienode_test.go new file mode 100644 index 0000000000..d6b80f61f5 --- /dev/null +++ b/triedb/pathdb/history_trienode_test.go @@ -0,0 +1,736 @@ +// 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 ( + "bytes" + "encoding/binary" + "math/rand" + "reflect" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/rawdb" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/internal/testrand" +) + +// randomTrienodes generates a random trienode set. +func randomTrienodes(n int) (map[common.Hash]map[string][]byte, common.Hash) { + var ( + root common.Hash + nodes = make(map[common.Hash]map[string][]byte) + ) + for i := 0; i < n; i++ { + owner := testrand.Hash() + if i == 0 { + owner = common.Hash{} + } + nodes[owner] = make(map[string][]byte) + + for j := 0; j < 10; j++ { + path := testrand.Bytes(rand.Intn(10)) + for z := 0; z < len(path); z++ { + nodes[owner][string(path[:z])] = testrand.Bytes(rand.Intn(128)) + } + } + // zero-size trie node, representing it was non-existent before + for j := 0; j < 10; j++ { + path := testrand.Bytes(32) + nodes[owner][string(path)] = nil + } + // root node with zero-size path + rnode := testrand.Bytes(256) + nodes[owner][""] = rnode + if owner == (common.Hash{}) { + root = crypto.Keccak256Hash(rnode) + } + } + return nodes, root +} + +func makeTrienodeHistory() *trienodeHistory { + nodes, root := randomTrienodes(10) + return newTrienodeHistory(root, common.Hash{}, 1, nodes) +} + +func makeTrienodeHistories(n int) []*trienodeHistory { + var ( + parent common.Hash + result []*trienodeHistory + ) + for i := 0; i < n; i++ { + nodes, root := randomTrienodes(10) + result = append(result, newTrienodeHistory(root, parent, uint64(i+1), nodes)) + parent = root + } + return result +} + +func TestEncodeDecodeTrienodeHistory(t *testing.T) { + var ( + dec trienodeHistory + obj = makeTrienodeHistory() + ) + header, keySection, valueSection, err := obj.encode() + if err != nil { + t.Fatalf("Failed to encode trienode history: %v", err) + } + if err := dec.decode(header, keySection, valueSection); err != nil { + t.Fatalf("Failed to decode trienode history: %v", err) + } + + if !reflect.DeepEqual(obj.meta, dec.meta) { + t.Fatal("trienode metadata is mismatched") + } + if !compareList(dec.owners, obj.owners) { + t.Fatal("trie owner list is mismatched") + } + if !compareMapList(dec.nodeList, obj.nodeList) { + t.Fatal("trienode list is mismatched") + } + if !compareMapSet(dec.nodes, obj.nodes) { + t.Fatal("trienode content is mismatched") + } + + // Re-encode again, ensuring the encoded blob still match + header2, keySection2, valueSection2, err := dec.encode() + if err != nil { + t.Fatalf("Failed to encode trienode history: %v", err) + } + if !bytes.Equal(header, header2) { + t.Fatal("re-encoded header is mismatched") + } + if !bytes.Equal(keySection, keySection2) { + t.Fatal("re-encoded key section is mismatched") + } + if !bytes.Equal(valueSection, valueSection2) { + t.Fatal("re-encoded value section is mismatched") + } +} + +func TestTrienodeHistoryReader(t *testing.T) { + var ( + hs = makeTrienodeHistories(10) + freezer, _ = rawdb.NewTrienodeFreezer(t.TempDir(), false, false) + ) + defer freezer.Close() + + for i, h := range hs { + header, keySection, valueSection, _ := h.encode() + if err := rawdb.WriteTrienodeHistory(freezer, uint64(i+1), header, keySection, valueSection); err != nil { + t.Fatalf("Failed to write trienode history: %v", err) + } + } + for i, h := range hs { + tr, err := newTrienodeHistoryReader(uint64(i+1), freezer) + if err != nil { + t.Fatalf("Failed to construct the history reader: %v", err) + } + for _, owner := range h.owners { + nodes := h.nodes[owner] + for key, value := range nodes { + blob, err := tr.read(owner, key) + if err != nil { + t.Fatalf("Failed to read trienode history: %v", err) + } + if !bytes.Equal(blob, value) { + t.Fatalf("Unexpected trie node data, want: %v, got: %v", value, blob) + } + } + } + } + for i, h := range hs { + metadata, err := readTrienodeMetadata(freezer, uint64(i+1)) + if err != nil { + t.Fatalf("Failed to read trienode history metadata: %v", err) + } + if !reflect.DeepEqual(h.meta, metadata) { + t.Fatalf("Unexpected trienode metadata, want: %v, got: %v", h.meta, metadata) + } + } +} + +// TestEmptyTrienodeHistory tests encoding/decoding of empty trienode history +func TestEmptyTrienodeHistory(t *testing.T) { + h := newTrienodeHistory(common.Hash{}, common.Hash{}, 1, make(map[common.Hash]map[string][]byte)) + + // Test encoding empty history + header, keySection, valueSection, err := h.encode() + if err != nil { + t.Fatalf("Failed to encode empty trienode history: %v", err) + } + + // Verify sections are minimal but valid + if len(header) == 0 { + t.Fatal("Header should not be empty") + } + if len(keySection) != 0 { + t.Fatal("Key section should be empty for empty history") + } + if len(valueSection) != 0 { + t.Fatal("Value section should be empty for empty history") + } + + // Test decoding empty history + var decoded trienodeHistory + if err := decoded.decode(header, keySection, valueSection); err != nil { + t.Fatalf("Failed to decode empty trienode history: %v", err) + } + + if len(decoded.owners) != 0 { + t.Fatal("Decoded history should have no owners") + } + if len(decoded.nodeList) != 0 { + t.Fatal("Decoded history should have no node lists") + } + if len(decoded.nodes) != 0 { + t.Fatal("Decoded history should have no nodes") + } +} + +// TestSingleTrieHistory tests encoding/decoding of history with single trie +func TestSingleTrieHistory(t *testing.T) { + nodes := make(map[common.Hash]map[string][]byte) + owner := testrand.Hash() + nodes[owner] = make(map[string][]byte) + + // Add some nodes with various sizes + nodes[owner][""] = testrand.Bytes(32) // empty key + nodes[owner]["a"] = testrand.Bytes(1) // small value + nodes[owner]["bb"] = testrand.Bytes(100) // medium value + nodes[owner]["ccc"] = testrand.Bytes(1000) // large value + nodes[owner]["dddd"] = testrand.Bytes(0) // empty value + + h := newTrienodeHistory(common.Hash{}, common.Hash{}, 1, nodes) + testEncodeDecode(t, h) +} + +// TestMultipleTries tests multiple tries with different node counts +func TestMultipleTries(t *testing.T) { + nodes := make(map[common.Hash]map[string][]byte) + + // First trie with many small nodes + owner1 := testrand.Hash() + nodes[owner1] = make(map[string][]byte) + for i := 0; i < 100; i++ { + key := string(testrand.Bytes(rand.Intn(10))) + nodes[owner1][key] = testrand.Bytes(rand.Intn(50)) + } + + // Second trie with few large nodes + owner2 := testrand.Hash() + nodes[owner2] = make(map[string][]byte) + for i := 0; i < 5; i++ { + key := string(testrand.Bytes(rand.Intn(20))) + nodes[owner2][key] = testrand.Bytes(1000 + rand.Intn(1000)) + } + + // Third trie with nil values (zero-size nodes) + owner3 := testrand.Hash() + nodes[owner3] = make(map[string][]byte) + for i := 0; i < 10; i++ { + key := string(testrand.Bytes(rand.Intn(15))) + nodes[owner3][key] = nil + } + + h := newTrienodeHistory(common.Hash{}, common.Hash{}, 1, nodes) + testEncodeDecode(t, h) +} + +// TestLargeNodeValues tests encoding/decoding with very large node values +func TestLargeNodeValues(t *testing.T) { + nodes := make(map[common.Hash]map[string][]byte) + owner := testrand.Hash() + nodes[owner] = make(map[string][]byte) + + // Test with progressively larger values + sizes := []int{1024, 10 * 1024, 100 * 1024, 1024 * 1024} // 1KB, 10KB, 100KB, 1MB + for _, size := range sizes { + key := string(testrand.Bytes(10)) + nodes[owner][key] = testrand.Bytes(size) + + h := newTrienodeHistory(common.Hash{}, common.Hash{}, 1, nodes) + testEncodeDecode(t, h) + t.Logf("Successfully tested encoding/decoding with %dKB value", size/1024) + } +} + +// TestNilNodeValues tests encoding/decoding with nil (zero-length) node values +func TestNilNodeValues(t *testing.T) { + nodes := make(map[common.Hash]map[string][]byte) + owner := testrand.Hash() + nodes[owner] = make(map[string][]byte) + + // Mix of nil and non-nil values + nodes[owner]["nil"] = nil + nodes[owner]["data1"] = []byte("some data") + nodes[owner]["data2"] = []byte("more data") + + h := newTrienodeHistory(common.Hash{}, common.Hash{}, 1, nodes) + testEncodeDecode(t, h) + + // Verify nil values are preserved + _, ok := h.nodes[owner]["nil"] + if !ok { + t.Fatal("Nil value should be preserved") + } +} + +// TestCorruptedHeader tests error handling for corrupted header data +func TestCorruptedHeader(t *testing.T) { + h := makeTrienodeHistory() + header, keySection, valueSection, _ := h.encode() + + // Test corrupted version + corruptedHeader := make([]byte, len(header)) + copy(corruptedHeader, header) + corruptedHeader[0] = 0xFF // Invalid version + + var decoded trienodeHistory + if err := decoded.decode(corruptedHeader, keySection, valueSection); err == nil { + t.Fatal("Expected error for corrupted version") + } + + // Test truncated header + truncatedHeader := header[:len(header)-5] + if err := decoded.decode(truncatedHeader, keySection, valueSection); err == nil { + t.Fatal("Expected error for truncated header") + } + + // Test header with invalid trie header size + invalidHeader := make([]byte, len(header)) + copy(invalidHeader, header) + invalidHeader = invalidHeader[:trienodeMetadataSize+5] // Not divisible by trie header size + + if err := decoded.decode(invalidHeader, keySection, valueSection); err == nil { + t.Fatal("Expected error for invalid header size") + } +} + +// TestCorruptedKeySection tests error handling for corrupted key section data +func TestCorruptedKeySection(t *testing.T) { + h := makeTrienodeHistory() + header, keySection, valueSection, _ := h.encode() + + // Test empty key section when header indicates data + if len(keySection) > 0 { + var decoded trienodeHistory + if err := decoded.decode(header, []byte{}, valueSection); err == nil { + t.Fatal("Expected error for empty key section with non-empty header") + } + } + + // Test truncated key section + if len(keySection) > 10 { + truncatedKeySection := keySection[:len(keySection)-10] + var decoded trienodeHistory + if err := decoded.decode(header, truncatedKeySection, valueSection); err == nil { + t.Fatal("Expected error for truncated key section") + } + } + + // Test corrupted key section with invalid varint + corruptedKeySection := make([]byte, len(keySection)) + copy(corruptedKeySection, keySection) + if len(corruptedKeySection) > 5 { + corruptedKeySection[5] = 0xFF // Corrupt varint encoding + var decoded trienodeHistory + if err := decoded.decode(header, corruptedKeySection, valueSection); err == nil { + t.Fatal("Expected error for corrupted varint in key section") + } + } +} + +// TestCorruptedValueSection tests error handling for corrupted value section data +func TestCorruptedValueSection(t *testing.T) { + h := makeTrienodeHistory() + header, keySection, valueSection, _ := h.encode() + + // Test truncated value section + if len(valueSection) > 10 { + truncatedValueSection := valueSection[:len(valueSection)-10] + var decoded trienodeHistory + if err := decoded.decode(header, keySection, truncatedValueSection); err == nil { + t.Fatal("Expected error for truncated value section") + } + } + + // Test empty value section when key section indicates data exists + if len(valueSection) > 0 { + var decoded trienodeHistory + if err := decoded.decode(header, keySection, []byte{}); err == nil { + t.Fatal("Expected error for empty value section with non-empty key section") + } + } +} + +// TestInvalidOffsets tests error handling for invalid offsets in encoded data +func TestInvalidOffsets(t *testing.T) { + h := makeTrienodeHistory() + header, keySection, valueSection, _ := h.encode() + + // Corrupt key offset in header (make it larger than key section) + corruptedHeader := make([]byte, len(header)) + copy(corruptedHeader, header) + corruptedHeader[trienodeMetadataSize+common.HashLength] = 0xff + + var dec1 trienodeHistory + if err := dec1.decode(corruptedHeader, keySection, valueSection); err == nil { + t.Fatal("Expected error for invalid key offset") + } + + // Corrupt value offset in header (make it larger than value section) + corruptedHeader = make([]byte, len(header)) + copy(corruptedHeader, header) + corruptedHeader[trienodeMetadataSize+common.HashLength+4] = 0xff + + var dec2 trienodeHistory + if err := dec2.decode(corruptedHeader, keySection, valueSection); err == nil { + t.Fatal("Expected error for invalid value offset") + } +} + +// TestTrienodeHistoryReaderNonExistentPath tests reading non-existent paths +func TestTrienodeHistoryReaderNonExistentPath(t *testing.T) { + var ( + h = makeTrienodeHistory() + freezer, _ = rawdb.NewTrienodeFreezer(t.TempDir(), false, false) + ) + defer freezer.Close() + + header, keySection, valueSection, _ := h.encode() + if err := rawdb.WriteTrienodeHistory(freezer, 1, header, keySection, valueSection); err != nil { + t.Fatalf("Failed to write trienode history: %v", err) + } + + tr, err := newTrienodeHistoryReader(1, freezer) + if err != nil { + t.Fatalf("Failed to construct history reader: %v", err) + } + + // Try to read a non-existent path + _, err = tr.read(testrand.Hash(), "nonexistent") + if err == nil { + t.Fatal("Expected error for non-existent trie owner") + } + + // Try to read from existing owner but non-existent path + owner := h.owners[0] + _, err = tr.read(owner, "nonexistent-path") + if err == nil { + t.Fatal("Expected error for non-existent path") + } +} + +// TestTrienodeHistoryReaderNilValues tests reading nil (zero-length) values +func TestTrienodeHistoryReaderNilValues(t *testing.T) { + nodes := make(map[common.Hash]map[string][]byte) + owner := testrand.Hash() + nodes[owner] = make(map[string][]byte) + + // Add some nil values + nodes[owner]["nil1"] = nil + nodes[owner]["nil2"] = nil + nodes[owner]["data1"] = []byte("some data") + + h := newTrienodeHistory(common.Hash{}, common.Hash{}, 1, nodes) + + var freezer, _ = rawdb.NewTrienodeFreezer(t.TempDir(), false, false) + defer freezer.Close() + + header, keySection, valueSection, _ := h.encode() + if err := rawdb.WriteTrienodeHistory(freezer, 1, header, keySection, valueSection); err != nil { + t.Fatalf("Failed to write trienode history: %v", err) + } + + tr, err := newTrienodeHistoryReader(1, freezer) + if err != nil { + t.Fatalf("Failed to construct history reader: %v", err) + } + + // Test reading nil values + data1, err := tr.read(owner, "nil1") + if err != nil { + t.Fatalf("Failed to read nil value: %v", err) + } + if len(data1) != 0 { + t.Fatal("Expected nil data for nil value") + } + + data2, err := tr.read(owner, "nil2") + if err != nil { + t.Fatalf("Failed to read nil value: %v", err) + } + if len(data2) != 0 { + t.Fatal("Expected nil data for nil value") + } + + // Test reading non-nil value + data3, err := tr.read(owner, "data1") + if err != nil { + t.Fatalf("Failed to read non-nil value: %v", err) + } + if !bytes.Equal(data3, []byte("some data")) { + t.Fatal("Data mismatch for non-nil value") + } +} + +// TestTrienodeHistoryReaderNilKey tests reading nil (zero-length) key +func TestTrienodeHistoryReaderNilKey(t *testing.T) { + nodes := make(map[common.Hash]map[string][]byte) + owner := testrand.Hash() + nodes[owner] = make(map[string][]byte) + + // Add some nil values + nodes[owner][""] = []byte("some data") + nodes[owner]["data1"] = []byte("some data") + + h := newTrienodeHistory(common.Hash{}, common.Hash{}, 1, nodes) + + var freezer, _ = rawdb.NewTrienodeFreezer(t.TempDir(), false, false) + defer freezer.Close() + + header, keySection, valueSection, _ := h.encode() + if err := rawdb.WriteTrienodeHistory(freezer, 1, header, keySection, valueSection); err != nil { + t.Fatalf("Failed to write trienode history: %v", err) + } + + tr, err := newTrienodeHistoryReader(1, freezer) + if err != nil { + t.Fatalf("Failed to construct history reader: %v", err) + } + + // Test reading nil values + data1, err := tr.read(owner, "") + if err != nil { + t.Fatalf("Failed to read nil value: %v", err) + } + if !bytes.Equal(data1, []byte("some data")) { + t.Fatal("Data mismatch for nil key") + } + + // Test reading non-nil value + data2, err := tr.read(owner, "data1") + if err != nil { + t.Fatalf("Failed to read non-nil value: %v", err) + } + if !bytes.Equal(data2, []byte("some data")) { + t.Fatal("Data mismatch for non-nil key") + } +} + +// TestTrienodeHistoryReaderIterator tests the iterator functionality +func TestTrienodeHistoryReaderIterator(t *testing.T) { + h := makeTrienodeHistory() + + // Count expected entries + expectedCount := 0 + expectedNodes := make(map[stateIdent]bool) + for owner, nodeList := range h.nodeList { + expectedCount += len(nodeList) + for _, node := range nodeList { + expectedNodes[stateIdent{ + typ: typeTrienode, + addressHash: owner, + path: node, + }] = true + } + } + + // Test the iterator + actualCount := 0 + for x := range h.forEach() { + _ = x + actualCount++ + } + if actualCount != expectedCount { + t.Fatalf("Iterator count mismatch: expected %d, got %d", expectedCount, actualCount) + } + + // Test that iterator yields expected state identifiers + seen := make(map[stateIdent]bool) + for ident := range h.forEach() { + if ident.typ != typeTrienode { + t.Fatal("Iterator should only yield trienode history identifiers") + } + key := stateIdent{typ: ident.typ, addressHash: ident.addressHash, path: ident.path} + if seen[key] { + t.Fatal("Iterator yielded duplicate identifier") + } + seen[key] = true + + if !expectedNodes[key] { + t.Fatalf("Unexpected yielded identifier %v", key) + } + } +} + +// TestSharedLen tests the sharedLen helper function +func TestSharedLen(t *testing.T) { + tests := []struct { + a, b []byte + expected int + }{ + // Empty strings + {[]byte(""), []byte(""), 0}, + // One empty string + {[]byte(""), []byte("abc"), 0}, + {[]byte("abc"), []byte(""), 0}, + // No common prefix + {[]byte("abc"), []byte("def"), 0}, + // Partial common prefix + {[]byte("abc"), []byte("abx"), 2}, + {[]byte("prefix"), []byte("pref"), 4}, + // Complete common prefix (shorter first) + {[]byte("ab"), []byte("abcd"), 2}, + // Complete common prefix (longer first) + {[]byte("abcd"), []byte("ab"), 2}, + // Identical strings + {[]byte("identical"), []byte("identical"), 9}, + // Binary data + {[]byte{0x00, 0x01, 0x02}, []byte{0x00, 0x01, 0x03}, 2}, + // Large strings + {bytes.Repeat([]byte("a"), 1000), bytes.Repeat([]byte("a"), 1000), 1000}, + {bytes.Repeat([]byte("a"), 1000), append(bytes.Repeat([]byte("a"), 999), []byte("b")...), 999}, + } + + for i, test := range tests { + result := sharedLen(test.a, test.b) + if result != test.expected { + t.Errorf("Test %d: sharedLen(%q, %q) = %d, expected %d", + i, test.a, test.b, result, test.expected) + } + // Test commutativity + resultReverse := sharedLen(test.b, test.a) + if result != resultReverse { + t.Errorf("Test %d: sharedLen is not commutative: sharedLen(a,b)=%d, sharedLen(b,a)=%d", + i, result, resultReverse) + } + } +} + +// TestDecodeHeaderCorruptedData tests decodeHeader with corrupted data +func TestDecodeHeaderCorruptedData(t *testing.T) { + // Create valid header data first + h := makeTrienodeHistory() + header, _, _, _ := h.encode() + + // Test with empty header + _, _, _, _, err := decodeHeader([]byte{}) + if err == nil { + t.Fatal("Expected error for empty header") + } + + // Test with invalid version + corruptedVersion := make([]byte, len(header)) + copy(corruptedVersion, header) + corruptedVersion[0] = 0xFF + _, _, _, _, err = decodeHeader(corruptedVersion) + if err == nil { + t.Fatal("Expected error for invalid version") + } + + // Test with truncated header (not divisible by trie header size) + truncated := header[:trienodeMetadataSize+5] + _, _, _, _, err = decodeHeader(truncated) + if err == nil { + t.Fatal("Expected error for truncated header") + } + + // Test with unordered trie owners + unordered := make([]byte, len(header)) + copy(unordered, header) + + // Swap two owner hashes to make them unordered + hash1Start := trienodeMetadataSize + hash2Start := trienodeMetadataSize + trienodeTrieHeaderSize + hash1 := unordered[hash1Start : hash1Start+common.HashLength] + hash2 := unordered[hash2Start : hash2Start+common.HashLength] + + // Only swap if they would be out of order + copy(unordered[hash1Start:hash1Start+common.HashLength], hash2) + copy(unordered[hash2Start:hash2Start+common.HashLength], hash1) + + _, _, _, _, err = decodeHeader(unordered) + if err == nil { + t.Fatal("Expected error for unordered trie owners") + } +} + +// TestDecodeSingleCorruptedData tests decodeSingle with corrupted data +func TestDecodeSingleCorruptedData(t *testing.T) { + h := makeTrienodeHistory() + _, keySection, _, _ := h.encode() + + // Test with empty key section + _, err := decodeSingle([]byte{}, nil) + if err == nil { + t.Fatal("Expected error for empty key section") + } + + // Test with key section too small for trailer + if len(keySection) > 0 { + _, err := decodeSingle(keySection[:3], nil) // Less than 4 bytes for trailer + if err == nil { + t.Fatal("Expected error for key section too small for trailer") + } + } + + // Test with corrupted varint in key section + corrupted := make([]byte, len(keySection)) + copy(corrupted, keySection) + corrupted[5] = 0xFF // Corrupt varint + _, err = decodeSingle(corrupted, nil) + if err == nil { + t.Fatal("Expected error for corrupted varint") + } + + // Test with corrupted trailer (invalid restart count) + corrupted = make([]byte, len(keySection)) + copy(corrupted, keySection) + // Set restart count to something too large + binary.BigEndian.PutUint32(corrupted[len(corrupted)-4:], 10000) + _, err = decodeSingle(corrupted, nil) + if err == nil { + t.Fatal("Expected error for invalid restart count") + } +} + +// Helper function to test encode/decode cycle +func testEncodeDecode(t *testing.T, h *trienodeHistory) { + header, keySection, valueSection, err := h.encode() + if err != nil { + t.Fatalf("Failed to encode trienode history: %v", err) + } + + var decoded trienodeHistory + if err := decoded.decode(header, keySection, valueSection); err != nil { + t.Fatalf("Failed to decode trienode history: %v", err) + } + + // Compare the decoded history with original + if !compareList(decoded.owners, h.owners) { + t.Fatal("Trie owner list mismatch") + } + if !compareMapList(decoded.nodeList, h.nodeList) { + t.Fatal("Trienode list mismatch") + } + if !compareMapSet(decoded.nodes, h.nodes) { + t.Fatal("Trienode content mismatch") + } +} diff --git a/triedb/pathdb/metrics.go b/triedb/pathdb/metrics.go index 779f9d813f..31c40053fc 100644 --- a/triedb/pathdb/metrics.go +++ b/triedb/pathdb/metrics.go @@ -69,12 +69,21 @@ var ( gcStorageMeter = metrics.NewRegisteredMeter("pathdb/gc/storage/count", nil) gcStorageBytesMeter = metrics.NewRegisteredMeter("pathdb/gc/storage/bytes", nil) - historyBuildTimeMeter = metrics.NewRegisteredResettingTimer("pathdb/history/time", nil) - historyDataBytesMeter = metrics.NewRegisteredMeter("pathdb/history/bytes/data", nil) - historyIndexBytesMeter = metrics.NewRegisteredMeter("pathdb/history/bytes/index", nil) - - indexHistoryTimer = metrics.NewRegisteredResettingTimer("pathdb/history/index/time", nil) - unindexHistoryTimer = metrics.NewRegisteredResettingTimer("pathdb/history/unindex/time", nil) + stateHistoryBuildTimeMeter = metrics.NewRegisteredResettingTimer("pathdb/history/state/time", nil) + stateHistoryDataBytesMeter = metrics.NewRegisteredMeter("pathdb/history/state/bytes/data", nil) + stateHistoryIndexBytesMeter = metrics.NewRegisteredMeter("pathdb/history/state/bytes/index", nil) + + //nolint:unused + trienodeHistoryBuildTimeMeter = metrics.NewRegisteredResettingTimer("pathdb/history/trienode/time", nil) + //nolint:unused + trienodeHistoryDataBytesMeter = metrics.NewRegisteredMeter("pathdb/history/trienode/bytes/data", nil) + //nolint:unused + trienodeHistoryIndexBytesMeter = metrics.NewRegisteredMeter("pathdb/history/trienode/bytes/index", nil) + + stateIndexHistoryTimer = metrics.NewRegisteredResettingTimer("pathdb/history/state/index/time", nil) + stateUnindexHistoryTimer = metrics.NewRegisteredResettingTimer("pathdb/history/state/unindex/time", nil) + trienodeIndexHistoryTimer = metrics.NewRegisteredResettingTimer("pathdb/history/trienode/index/time", nil) + trienodeUnindexHistoryTimer = metrics.NewRegisteredResettingTimer("pathdb/history/trienode/unindex/time", nil) lookupAddLayerTimer = metrics.NewRegisteredResettingTimer("pathdb/lookup/add/time", nil) lookupRemoveLayerTimer = metrics.NewRegisteredResettingTimer("pathdb/lookup/remove/time", nil) From 659342a52300d9cd8218face5528a91e7434a8fb Mon Sep 17 00:00:00 2001 From: 10gic Date: Fri, 10 Oct 2025 17:47:33 +0800 Subject: [PATCH 41/61] ethclient: add SubscribeTransactionReceipts (#32869) Add `SubscribeTransactionReceipts` for ethclient. This is a complement to https://github.com/ethereum/go-ethereum/pull/32697. --- eth/filters/api.go | 24 ++++++++++++++++++------ ethclient/ethclient.go | 9 +++++++++ interfaces.go | 12 ++++++++++++ 3 files changed, 39 insertions(+), 6 deletions(-) diff --git a/eth/filters/api.go b/eth/filters/api.go index 9f9209aea7..a3ed00f33b 100644 --- a/eth/filters/api.go +++ b/eth/filters/api.go @@ -299,15 +299,27 @@ func (api *FilterAPI) Logs(ctx context.Context, crit FilterCriteria) (*rpc.Subsc return rpcSub, nil } -// TransactionReceiptsFilter defines criteria for transaction receipts subscription. -// If TransactionHashes is nil or empty, receipts for all transactions included in new blocks will be delivered. -// Otherwise, only receipts for the specified transactions will be delivered. -type TransactionReceiptsFilter struct { - TransactionHashes []common.Hash `json:"transactionHashes,omitempty"` +// TransactionReceiptsQuery defines criteria for transaction receipts subscription. +// Same as ethereum.TransactionReceiptsQuery but with UnmarshalJSON() method. +type TransactionReceiptsQuery ethereum.TransactionReceiptsQuery + +// UnmarshalJSON sets *args fields with given data. +func (args *TransactionReceiptsQuery) UnmarshalJSON(data []byte) error { + type input struct { + TransactionHashes []common.Hash `json:"transactionHashes"` + } + + var raw input + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + + args.TransactionHashes = raw.TransactionHashes + return nil } // TransactionReceipts creates a subscription that fires transaction receipts when transactions are included in blocks. -func (api *FilterAPI) TransactionReceipts(ctx context.Context, filter *TransactionReceiptsFilter) (*rpc.Subscription, error) { +func (api *FilterAPI) TransactionReceipts(ctx context.Context, filter *TransactionReceiptsQuery) (*rpc.Subscription, error) { notifier, supported := rpc.NotifierFromContext(ctx) if !supported { return &rpc.Subscription{}, rpc.ErrNotificationsUnsupported diff --git a/ethclient/ethclient.go b/ethclient/ethclient.go index 1195929f7d..8b26f5b3ca 100644 --- a/ethclient/ethclient.go +++ b/ethclient/ethclient.go @@ -350,6 +350,15 @@ func (ec *Client) TransactionReceipt(ctx context.Context, txHash common.Hash) (* return r, err } +// SubscribeTransactionReceipts subscribes to notifications about transaction receipts. +func (ec *Client) SubscribeTransactionReceipts(ctx context.Context, q *ethereum.TransactionReceiptsQuery, ch chan<- []*types.Receipt) (ethereum.Subscription, error) { + sub, err := ec.c.EthSubscribe(ctx, ch, "transactionReceipts", q) + if err != nil { + return nil, err + } + return sub, nil +} + // SyncProgress retrieves the current progress of the sync algorithm. If there's // no sync currently running, it returns nil. func (ec *Client) SyncProgress(ctx context.Context) (*ethereum.SyncProgress, error) { diff --git a/interfaces.go b/interfaces.go index be5b970851..2828af1cc9 100644 --- a/interfaces.go +++ b/interfaces.go @@ -62,6 +62,13 @@ type ChainReader interface { SubscribeNewHead(ctx context.Context, ch chan<- *types.Header) (Subscription, error) } +// TransactionReceiptsQuery defines criteria for transaction receipts subscription. +// If TransactionHashes is empty, receipts for all transactions included in new blocks will be delivered. +// Otherwise, only receipts for the specified transactions will be delivered. +type TransactionReceiptsQuery struct { + TransactionHashes []common.Hash +} + // TransactionReader provides access to past transactions and their receipts. // Implementations may impose arbitrary restrictions on the transactions and receipts that // can be retrieved. Historic transactions may not be available. @@ -81,6 +88,11 @@ type TransactionReader interface { // transaction may not be included in the current canonical chain even if a receipt // exists. TransactionReceipt(ctx context.Context, txHash common.Hash) (*types.Receipt, error) + // SubscribeTransactionReceipts subscribes to notifications about transaction receipts. + // The receipts are delivered in batches when transactions are included in blocks. + // If q is nil or has empty TransactionHashes, all receipts from new blocks will be delivered. + // Otherwise, only receipts for the specified transaction hashes will be delivered. + SubscribeTransactionReceipts(ctx context.Context, q *TransactionReceiptsQuery, ch chan<- []*types.Receipt) (Subscription, error) } // ChainStateReader wraps access to the state trie of the canonical blockchain. Note that From a3aae29845736cbf40c2bedbdee1c3396dd2f88c Mon Sep 17 00:00:00 2001 From: Luke Ma Date: Mon, 13 Oct 2025 15:26:35 +0800 Subject: [PATCH 42/61] node: fix error condition in gzipResponseWriter.init() (#32896) --- node/rpcstack.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/node/rpcstack.go b/node/rpcstack.go index 655f7db9e4..a1cc832f9f 100644 --- a/node/rpcstack.go +++ b/node/rpcstack.go @@ -500,7 +500,7 @@ func (w *gzipResponseWriter) init() { hdr := w.resp.Header() length := hdr.Get("content-length") if len(length) > 0 { - if n, err := strconv.ParseUint(length, 10, 64); err != nil { + if n, err := strconv.ParseUint(length, 10, 64); err == nil { w.hasLength = true w.contentLength = n } From 2010781c2998557ccf9883155b6ff4e73d1d64a0 Mon Sep 17 00:00:00 2001 From: cui Date: Mon, 13 Oct 2025 16:39:10 +0800 Subject: [PATCH 43/61] core/types: optimize MergeBloom by using bitutil (#32882) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ``` goos: darwin goarch: amd64 pkg: github.com/ethereum/go-ethereum/core/types cpu: VirtualApple @ 2.50GHz │ old.txt │ new.txt │ │ sec/op │ sec/op vs base │ CreateBloom/small-createbloom-10 1.676µ ± 4% 1.646µ ± 1% -1.76% (p=0.000 n=10) CreateBloom/large-createbloom-10 164.8µ ± 3% 164.3µ ± 0% ~ (p=0.247 n=10) CreateBloom/small-mergebloom-10 231.60n ± 0% 68.00n ± 0% -70.64% (p=0.000 n=10) CreateBloom/large-mergebloom-10 21.803µ ± 3% 5.107µ ± 1% -76.58% (p=0.000 n=10) geomean 6.111µ 3.113µ -49.06% │ old.txt │ new.txt │ │ B/op │ B/op vs base │ CreateBloom/small-createbloom-10 112.0 ± 0% 112.0 ± 0% ~ (p=1.000 n=10) ¹ CreateBloom/large-createbloom-10 10.94Ki ± 0% 10.94Ki ± 0% ~ (p=0.474 n=10) CreateBloom/small-mergebloom-10 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=10) ¹ CreateBloom/large-mergebloom-10 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=10) ¹ geomean ² +0.00% ² ¹ all samples are equal ² summaries must be >0 to compute geomean │ old.txt │ new.txt │ │ allocs/op │ allocs/op vs base │ CreateBloom/small-createbloom-10 6.000 ± 0% 6.000 ± 0% ~ (p=1.000 n=10) ¹ CreateBloom/large-createbloom-10 600.0 ± 0% 600.0 ± 0% ~ (p=1.000 n=10) ¹ CreateBloom/small-mergebloom-10 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=10) ¹ CreateBloom/large-mergebloom-10 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=10) ¹ geomean ² +0.00% ² ¹ all samples are equal ² summaries must be >0 to compute geomean ``` --- core/types/bloom9.go | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/core/types/bloom9.go b/core/types/bloom9.go index 5a6e49c220..1d57e8e4bc 100644 --- a/core/types/bloom9.go +++ b/core/types/bloom9.go @@ -21,6 +21,7 @@ import ( "fmt" "math/big" + "github.com/ethereum/go-ethereum/common/bitutil" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/crypto" ) @@ -125,9 +126,7 @@ func MergeBloom(receipts Receipts) Bloom { for _, receipt := range receipts { if len(receipt.Logs) != 0 { bl := receipt.Bloom.Bytes() - for i := range bin { - bin[i] |= bl[i] - } + bitutil.ORBytes(bin[:], bin[:], bl) } } return bin From 85e9977faecd23909b0373ae4c8268e8bf62b6a3 Mon Sep 17 00:00:00 2001 From: Delweng Date: Mon, 13 Oct 2025 16:40:08 +0800 Subject: [PATCH 44/61] p2p: rm unused var seedMinTableTime (#32876) --- p2p/discover/table.go | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/p2p/discover/table.go b/p2p/discover/table.go index 6a1c7494ee..e5b2c7c8c5 100644 --- a/p2p/discover/table.go +++ b/p2p/discover/table.go @@ -54,9 +54,8 @@ const ( bucketIPLimit, bucketSubnet = 2, 24 // at most 2 addresses from the same /24 tableIPLimit, tableSubnet = 10, 24 - seedMinTableTime = 5 * time.Minute - seedCount = 30 - seedMaxAge = 5 * 24 * time.Hour + seedCount = 30 + seedMaxAge = 5 * 24 * time.Hour ) // Table is the 'node table', a Kademlia-like index of neighbor nodes. The table keeps From bc0a21a1d5e69513530cbe781b73c6b116856627 Mon Sep 17 00:00:00 2001 From: Lewis Date: Mon, 13 Oct 2025 20:10:44 +0900 Subject: [PATCH 45/61] eth/filters: uninstall subscription in filter apis on error (#32894) Fix https://github.com/ethereum/go-ethereum/issues/32893. In the previous https://github.com/ethereum/go-ethereum/pull/32794, it only handles the pending tx filter, while there are also head and log filters. This PR applies the patch to all filter APIs and uses `defer` to maintain code consistency. --- eth/filters/api.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/eth/filters/api.go b/eth/filters/api.go index a3ed00f33b..58baf2c3aa 100644 --- a/eth/filters/api.go +++ b/eth/filters/api.go @@ -143,6 +143,7 @@ func (api *FilterAPI) NewPendingTransactionFilter(fullTx *bool) rpc.ID { api.filtersMu.Unlock() go func() { + defer pendingTxSub.Unsubscribe() for { select { case pTx := <-pendingTxs: @@ -155,7 +156,6 @@ func (api *FilterAPI) NewPendingTransactionFilter(fullTx *bool) rpc.ID { api.filtersMu.Lock() delete(api.filters, pendingTxSub.ID) api.filtersMu.Unlock() - pendingTxSub.Unsubscribe() return } } @@ -218,6 +218,7 @@ func (api *FilterAPI) NewBlockFilter() rpc.ID { api.filtersMu.Unlock() go func() { + defer headerSub.Unsubscribe() for { select { case h := <-headers: @@ -403,6 +404,7 @@ func (api *FilterAPI) NewFilter(crit FilterCriteria) (rpc.ID, error) { api.filtersMu.Unlock() go func() { + defer logsSub.Unsubscribe() for { select { case l := <-logs: From a7359ceb69fcd183e59f651e34aae873cf03e5a0 Mon Sep 17 00:00:00 2001 From: Delweng Date: Mon, 13 Oct 2025 19:40:03 +0800 Subject: [PATCH 46/61] triedb, core/rawdb: implement the partial read in freezer (#32132) This PR implements the partial read functionalities in the freezer, optimizing the state history reader by resolving less data from freezer. --------- Signed-off-by: jsvisa Co-authored-by: Gary Rong --- core/rawdb/accessors_state.go | 24 +++--------- core/rawdb/chain_freezer.go | 4 ++ core/rawdb/database.go | 6 +++ core/rawdb/freezer.go | 9 +++++ core/rawdb/freezer_memory.go | 25 ++++++++++++ core/rawdb/freezer_resettable.go | 9 +++++ core/rawdb/freezer_table.go | 65 ++++++++++++++++++++++++++++++++ core/rawdb/freezer_table_test.go | 62 ++++++++++++++++++++++++++++++ core/rawdb/table.go | 6 +++ ethdb/database.go | 4 ++ ethdb/remotedb/remotedb.go | 4 ++ triedb/pathdb/history_reader.go | 38 +++++++------------ 12 files changed, 214 insertions(+), 42 deletions(-) diff --git a/core/rawdb/accessors_state.go b/core/rawdb/accessors_state.go index 298ad04f40..714c1f77d6 100644 --- a/core/rawdb/accessors_state.go +++ b/core/rawdb/accessors_state.go @@ -188,24 +188,16 @@ func ReadStateAccountIndex(db ethdb.AncientReaderOp, id uint64) []byte { // data in the concatenated storage data table. Compute the position of state // history in freezer by minus one since the id of first state history starts // from one (zero for initial state). -func ReadStateStorageIndex(db ethdb.AncientReaderOp, id uint64) []byte { - blob, err := db.Ancient(stateHistoryStorageIndex, id-1) - if err != nil { - return nil - } - return blob +func ReadStateStorageIndex(db ethdb.AncientReaderOp, id uint64, offset, length int) ([]byte, error) { + return db.AncientBytes(stateHistoryStorageIndex, id-1, uint64(offset), uint64(length)) } // ReadStateAccountHistory retrieves the concatenated account data blob for the // specified state history. Offsets and lengths are resolved via the account // index. Compute the position of state history in freezer by minus one since // the id of first state history starts from one (zero for initial state). -func ReadStateAccountHistory(db ethdb.AncientReaderOp, id uint64) []byte { - blob, err := db.Ancient(stateHistoryAccountData, id-1) - if err != nil { - return nil - } - return blob +func ReadStateAccountHistory(db ethdb.AncientReaderOp, id uint64, offset, length int) ([]byte, error) { + return db.AncientBytes(stateHistoryAccountData, id-1, uint64(offset), uint64(length)) } // ReadStateStorageHistory retrieves the concatenated storage slot data blob for @@ -213,12 +205,8 @@ func ReadStateAccountHistory(db ethdb.AncientReaderOp, id uint64) []byte { // storage indexes. Compute the position of state history in freezer by minus // one since the id of first state history starts from one (zero for initial // state). -func ReadStateStorageHistory(db ethdb.AncientReaderOp, id uint64) []byte { - blob, err := db.Ancient(stateHistoryStorageData, id-1) - if err != nil { - return nil - } - return blob +func ReadStateStorageHistory(db ethdb.AncientReaderOp, id uint64, offset, length int) ([]byte, error) { + return db.AncientBytes(stateHistoryStorageData, id-1, uint64(offset), uint64(length)) } // ReadStateHistory retrieves the state history from database with provided id. diff --git a/core/rawdb/chain_freezer.go b/core/rawdb/chain_freezer.go index c12f2ab8fe..d33f7ce33d 100644 --- a/core/rawdb/chain_freezer.go +++ b/core/rawdb/chain_freezer.go @@ -403,6 +403,10 @@ func (f *chainFreezer) AncientRange(kind string, start, count, maxBytes uint64) return f.ancients.AncientRange(kind, start, count, maxBytes) } +func (f *chainFreezer) AncientBytes(kind string, id, offset, length uint64) ([]byte, error) { + return f.ancients.AncientBytes(kind, id, offset, length) +} + func (f *chainFreezer) ModifyAncients(fn func(ethdb.AncientWriteOp) error) (int64, error) { return f.ancients.ModifyAncients(fn) } diff --git a/core/rawdb/database.go b/core/rawdb/database.go index 626d390c0d..724c90ead6 100644 --- a/core/rawdb/database.go +++ b/core/rawdb/database.go @@ -100,6 +100,12 @@ func (db *nofreezedb) AncientRange(kind string, start, max, maxByteSize uint64) return nil, errNotSupported } +// AncientBytes retrieves the value segment of the element specified by the id +// and value offsets. +func (db *nofreezedb) AncientBytes(kind string, id, offset, length uint64) ([]byte, error) { + return nil, errNotSupported +} + // Ancients returns an error as we don't have a backing chain freezer. func (db *nofreezedb) Ancients() (uint64, error) { return 0, errNotSupported diff --git a/core/rawdb/freezer.go b/core/rawdb/freezer.go index 98ad174ce0..42cd2a7999 100644 --- a/core/rawdb/freezer.go +++ b/core/rawdb/freezer.go @@ -202,6 +202,15 @@ func (f *Freezer) AncientRange(kind string, start, count, maxBytes uint64) ([][] return nil, errUnknownTable } +// AncientBytes retrieves the value segment of the element specified by the id +// and value offsets. +func (f *Freezer) AncientBytes(kind string, id, offset, length uint64) ([]byte, error) { + if table := f.tables[kind]; table != nil { + return table.RetrieveBytes(id, offset, length) + } + return nil, errUnknownTable +} + // Ancients returns the length of the frozen items. func (f *Freezer) Ancients() (uint64, error) { return f.frozen.Load(), nil diff --git a/core/rawdb/freezer_memory.go b/core/rawdb/freezer_memory.go index f5621ac4c6..8cb4cc2006 100644 --- a/core/rawdb/freezer_memory.go +++ b/core/rawdb/freezer_memory.go @@ -412,3 +412,28 @@ func (f *MemoryFreezer) Reset() error { func (f *MemoryFreezer) AncientDatadir() (string, error) { return "", nil } + +// AncientBytes retrieves the value segment of the element specified by the id +// and value offsets. +func (f *MemoryFreezer) AncientBytes(kind string, id, offset, length uint64) ([]byte, error) { + f.lock.RLock() + defer f.lock.RUnlock() + + table := f.tables[kind] + if table == nil { + return nil, errUnknownTable + } + entries, err := table.retrieve(id, 1, 0) + if err != nil { + return nil, err + } + if len(entries) == 0 { + return nil, errOutOfBounds + } + data := entries[0] + + if offset > uint64(len(data)) || offset+length > uint64(len(data)) { + return nil, fmt.Errorf("requested range out of bounds: item size %d, offset %d, length %d", len(data), offset, length) + } + return data[offset : offset+length], nil +} diff --git a/core/rawdb/freezer_resettable.go b/core/rawdb/freezer_resettable.go index 9db71cfd0e..f531e668c3 100644 --- a/core/rawdb/freezer_resettable.go +++ b/core/rawdb/freezer_resettable.go @@ -126,6 +126,15 @@ func (f *resettableFreezer) AncientRange(kind string, start, count, maxBytes uin return f.freezer.AncientRange(kind, start, count, maxBytes) } +// AncientBytes retrieves the value segment of the element specified by the id +// and value offsets. +func (f *resettableFreezer) AncientBytes(kind string, id, offset, length uint64) ([]byte, error) { + f.lock.RLock() + defer f.lock.RUnlock() + + return f.freezer.AncientBytes(kind, id, offset, length) +} + // Ancients returns the length of the frozen items. func (f *resettableFreezer) Ancients() (uint64, error) { f.lock.RLock() diff --git a/core/rawdb/freezer_table.go b/core/rawdb/freezer_table.go index d3a29a73c6..01a754c5c8 100644 --- a/core/rawdb/freezer_table.go +++ b/core/rawdb/freezer_table.go @@ -1107,6 +1107,71 @@ func (t *freezerTable) retrieveItems(start, count, maxBytes uint64) ([]byte, []i return output, sizes, nil } +// RetrieveBytes retrieves the value segment of the element specified by the id +// and value offsets. +func (t *freezerTable) RetrieveBytes(item, offset, length uint64) ([]byte, error) { + t.lock.RLock() + defer t.lock.RUnlock() + + if t.index == nil || t.head == nil || t.metadata.file == nil { + return nil, errClosed + } + items, hidden := t.items.Load(), t.itemHidden.Load() + if items <= item || hidden > item { + return nil, errOutOfBounds + } + + // Retrieves the index entries for the specified ID and its immediate successor + indices, err := t.getIndices(item, 1) + if err != nil { + return nil, err + } + index0, index1 := indices[0], indices[1] + + itemStart, itemLimit, fileId := index0.bounds(index1) + itemSize := itemLimit - itemStart + + dataFile, exist := t.files[fileId] + if !exist { + return nil, fmt.Errorf("missing data file %d", fileId) + } + + // Perform the partial read if no-compression was enabled upon + if t.config.noSnappy { + if offset > uint64(itemSize) || offset+length > uint64(itemSize) { + return nil, fmt.Errorf("requested range out of bounds: item size %d, offset %d, length %d", itemSize, offset, length) + } + itemStart += uint32(offset) + + buf := make([]byte, length) + _, err = dataFile.ReadAt(buf, int64(itemStart)) + if err != nil { + return nil, err + } + t.readMeter.Mark(int64(length)) + return buf, nil + } else { + // If compressed, read the full item, decompress, then slice. + // Unfortunately, in this case, there is no performance gain + // by performing the partial read at all. + buf := make([]byte, itemSize) + _, err = dataFile.ReadAt(buf, int64(itemStart)) + if err != nil { + return nil, err + } + t.readMeter.Mark(int64(itemSize)) + + data, err := snappy.Decode(nil, buf) + if err != nil { + return nil, err + } + if offset > uint64(len(data)) || offset+length > uint64(len(data)) { + return nil, fmt.Errorf("requested range out of bounds: item size %d, offset %d, length %d", len(data), offset, length) + } + return data[offset : offset+length], nil + } +} + // size returns the total data size in the freezer table. func (t *freezerTable) size() (uint64, error) { t.lock.RLock() diff --git a/core/rawdb/freezer_table_test.go b/core/rawdb/freezer_table_test.go index 96edac7e4a..fc21ea6c63 100644 --- a/core/rawdb/freezer_table_test.go +++ b/core/rawdb/freezer_table_test.go @@ -1571,3 +1571,65 @@ func TestTailTruncationCrash(t *testing.T) { t.Fatalf("Unexpected index flush offset, want: %d, got: %d", 26*indexEntrySize, f.metadata.flushOffset) } } + +func TestFreezerAncientBytes(t *testing.T) { + t.Parallel() + types := []struct { + name string + config freezerTableConfig + }{ + {"uncompressed", freezerTableConfig{noSnappy: true}}, + {"compressed", freezerTableConfig{noSnappy: false}}, + } + for _, typ := range types { + t.Run(typ.name, func(t *testing.T) { + f, err := newTable(os.TempDir(), fmt.Sprintf("ancientbytes-%s-%d", typ.name, rand.Uint64()), metrics.NewMeter(), metrics.NewMeter(), metrics.NewGauge(), 1000, typ.config, false) + if err != nil { + t.Fatal(err) + } + defer f.Close() + + for i := 0; i < 10; i++ { + data := getChunk(100, i) + batch := f.newBatch() + require.NoError(t, batch.AppendRaw(uint64(i), data)) + require.NoError(t, batch.commit()) + } + + for i := 0; i < 10; i++ { + full, err := f.Retrieve(uint64(i)) + require.NoError(t, err) + + // Full read + got, err := f.RetrieveBytes(uint64(i), 0, uint64(len(full))) + require.NoError(t, err) + if !bytes.Equal(got, full) { + t.Fatalf("full read mismatch for entry %d", i) + } + // Empty read + got, err = f.RetrieveBytes(uint64(i), 0, 0) + require.NoError(t, err) + if !bytes.Equal(got, full[:0]) { + t.Fatalf("empty read mismatch for entry %d", i) + } + // Middle slice + got, err = f.RetrieveBytes(uint64(i), 10, 50) + require.NoError(t, err) + if !bytes.Equal(got, full[10:60]) { + t.Fatalf("middle slice mismatch for entry %d", i) + } + // Single byte + got, err = f.RetrieveBytes(uint64(i), 99, 1) + require.NoError(t, err) + if !bytes.Equal(got, full[99:100]) { + t.Fatalf("single byte mismatch for entry %d", i) + } + // Out of bounds + _, err = f.RetrieveBytes(uint64(i), 100, 1) + if err == nil { + t.Fatalf("expected error for out-of-bounds read for entry %d", i) + } + } + }) + } +} diff --git a/core/rawdb/table.go b/core/rawdb/table.go index 45c8aecf0c..d38afdaa35 100644 --- a/core/rawdb/table.go +++ b/core/rawdb/table.go @@ -62,6 +62,12 @@ func (t *table) AncientRange(kind string, start, count, maxBytes uint64) ([][]by return t.db.AncientRange(kind, start, count, maxBytes) } +// AncientBytes is a noop passthrough that just forwards the request to the underlying +// database. +func (t *table) AncientBytes(kind string, id, offset, length uint64) ([]byte, error) { + return t.db.AncientBytes(kind, id, offset, length) +} + // Ancients is a noop passthrough that just forwards the request to the underlying // database. func (t *table) Ancients() (uint64, error) { diff --git a/ethdb/database.go b/ethdb/database.go index e665a84a61..534fcad4fc 100644 --- a/ethdb/database.go +++ b/ethdb/database.go @@ -121,6 +121,10 @@ type AncientReaderOp interface { // - if maxBytes is not specified, 'count' items will be returned if they are present AncientRange(kind string, start, count, maxBytes uint64) ([][]byte, error) + // AncientBytes retrieves the value segment of the element specified by the id + // and value offsets. + AncientBytes(kind string, id, offset, length uint64) ([]byte, error) + // Ancients returns the ancient item numbers in the ancient store. Ancients() (uint64, error) diff --git a/ethdb/remotedb/remotedb.go b/ethdb/remotedb/remotedb.go index 7fe154ea95..0d0d854fe4 100644 --- a/ethdb/remotedb/remotedb.go +++ b/ethdb/remotedb/remotedb.go @@ -140,6 +140,10 @@ func (db *Database) Close() error { return nil } +func (db *Database) AncientBytes(kind string, id, offset, length uint64) ([]byte, error) { + panic("not supported") +} + func New(client *rpc.Client) ethdb.Database { if client == nil { return nil diff --git a/triedb/pathdb/history_reader.go b/triedb/pathdb/history_reader.go index ce6aa693d1..1bf4cf648d 100644 --- a/triedb/pathdb/history_reader.go +++ b/triedb/pathdb/history_reader.go @@ -144,25 +144,17 @@ func (r *historyReader) readAccountMetadata(address common.Address, historyID ui // readStorageMetadata resolves the storage slot metadata within the specified // state history. func (r *historyReader) readStorageMetadata(storageKey common.Hash, storageHash common.Hash, historyID uint64, slotOffset, slotNumber int) ([]byte, error) { - // TODO(rj493456442) optimize it with partial read - blob := rawdb.ReadStateStorageIndex(r.freezer, historyID) - if len(blob) == 0 { - return nil, fmt.Errorf("storage index is truncated, historyID: %d", historyID) - } - if len(blob)%slotIndexSize != 0 { - return nil, fmt.Errorf("storage indices is corrupted, historyID: %d, size: %d", historyID, len(blob)) - } - if slotIndexSize*(slotOffset+slotNumber) > len(blob) { - return nil, fmt.Errorf("storage indices is truncated, historyID: %d, size: %d, offset: %d, length: %d", historyID, len(blob), slotOffset, slotNumber) + data, err := rawdb.ReadStateStorageIndex(r.freezer, historyID, slotIndexSize*slotOffset, slotIndexSize*slotNumber) + if err != nil { + msg := fmt.Sprintf("id: %d, slot-offset: %d, slot-length: %d", historyID, slotOffset, slotNumber) + return nil, fmt.Errorf("storage indices corrupted, %s, %w", msg, err) } - subSlice := blob[slotIndexSize*slotOffset : slotIndexSize*(slotOffset+slotNumber)] - // TODO(rj493456442) get rid of the metadata resolution var ( m meta target common.Hash ) - blob = rawdb.ReadStateHistoryMeta(r.freezer, historyID) + blob := rawdb.ReadStateHistoryMeta(r.freezer, historyID) if err := m.decode(blob); err != nil { return nil, err } @@ -172,17 +164,17 @@ func (r *historyReader) readStorageMetadata(storageKey common.Hash, storageHash target = storageKey } pos := sort.Search(slotNumber, func(i int) bool { - slotID := subSlice[slotIndexSize*i : slotIndexSize*i+common.HashLength] + slotID := data[slotIndexSize*i : slotIndexSize*i+common.HashLength] return bytes.Compare(slotID, target.Bytes()) >= 0 }) if pos == slotNumber { return nil, fmt.Errorf("storage metadata is not found, slot key: %#x, historyID: %d", storageKey, historyID) } offset := slotIndexSize * pos - if target != common.BytesToHash(subSlice[offset:offset+common.HashLength]) { + if target != common.BytesToHash(data[offset:offset+common.HashLength]) { return nil, fmt.Errorf("storage metadata is not found, slot key: %#x, historyID: %d", storageKey, historyID) } - return subSlice[offset : slotIndexSize*(pos+1)], nil + return data[offset : slotIndexSize*(pos+1)], nil } // readAccount retrieves the account data from the specified state history. @@ -194,12 +186,11 @@ func (r *historyReader) readAccount(address common.Address, historyID uint64) ([ length := int(metadata[common.AddressLength]) // one byte for account data length offset := int(binary.BigEndian.Uint32(metadata[common.AddressLength+1 : common.AddressLength+5])) // four bytes for the account data offset - // TODO(rj493456442) optimize it with partial read - data := rawdb.ReadStateAccountHistory(r.freezer, historyID) - if len(data) < length+offset { + data, err := rawdb.ReadStateAccountHistory(r.freezer, historyID, offset, length) + if err != nil { return nil, fmt.Errorf("account data is truncated, address: %#x, historyID: %d, size: %d, offset: %d, len: %d", address, historyID, len(data), offset, length) } - return data[offset : offset+length], nil + return data, nil } // readStorage retrieves the storage slot data from the specified state history. @@ -222,12 +213,11 @@ func (r *historyReader) readStorage(address common.Address, storageKey common.Ha length := int(slotMetadata[common.HashLength]) // one byte for slot data length offset := int(binary.BigEndian.Uint32(slotMetadata[common.HashLength+1 : common.HashLength+5])) // four bytes for slot data offset - // TODO(rj493456442) optimize it with partial read - data := rawdb.ReadStateStorageHistory(r.freezer, historyID) - if len(data) < offset+length { + data, err := rawdb.ReadStateStorageHistory(r.freezer, historyID, offset, length) + if err != nil { return nil, fmt.Errorf("storage data is truncated, address: %#x, key: %#x, historyID: %d, size: %d, offset: %d, len: %d", address, storageKey, historyID, len(data), offset, length) } - return data[offset : offset+length], nil + return data, nil } // read retrieves the state element data associated with the stateID. From 5c6ba6b40042bf6fdf7a36d2620b658d041bd3ee Mon Sep 17 00:00:00 2001 From: cui Date: Mon, 13 Oct 2025 20:00:43 +0800 Subject: [PATCH 47/61] p2p/enode: optimize LogDist (#32887) This speeds up LogDist by 75% using 64-bit operations instead of byte-wise XOR. --------- Co-authored-by: Felix Lange --- p2p/enode/node.go | 11 +++++++---- p2p/enode/node_test.go | 22 ++++++++++++++++++++++ 2 files changed, 29 insertions(+), 4 deletions(-) diff --git a/p2p/enode/node.go b/p2p/enode/node.go index d6f2ac7ff5..8198050353 100644 --- a/p2p/enode/node.go +++ b/p2p/enode/node.go @@ -19,6 +19,7 @@ package enode import ( "crypto/ecdsa" "encoding/base64" + "encoding/binary" "encoding/hex" "errors" "fmt" @@ -373,12 +374,14 @@ func DistCmp(target, a, b ID) int { // LogDist returns the logarithmic distance between a and b, log2(a ^ b). func LogDist(a, b ID) int { lz := 0 - for i := range a { - x := a[i] ^ b[i] + for i := 0; i < len(a); i += 8 { + ai := binary.BigEndian.Uint64(a[i : i+8]) + bi := binary.BigEndian.Uint64(b[i : i+8]) + x := ai ^ bi if x == 0 { - lz += 8 + lz += 64 } else { - lz += bits.LeadingZeros8(x) + lz += bits.LeadingZeros64(x) break } } diff --git a/p2p/enode/node_test.go b/p2p/enode/node_test.go index e9fe631f34..f276af6638 100644 --- a/p2p/enode/node_test.go +++ b/p2p/enode/node_test.go @@ -378,6 +378,28 @@ func TestID_logdist(t *testing.T) { } } +func makeIDs() (ID, ID) { + var a, b ID + size := len(a) + // last byte differs + for i := 0; i < size-1; i++ { + a[i] = 0xAA + b[i] = 0xAA + } + a[size-1] = 0xAA + b[size-1] = 0xAB + return a, b +} + +// Benchmark LogDist +func BenchmarkLogDist(b *testing.B) { + aID, bID := makeIDs() // 256-bit ID + b.ResetTimer() + for i := 0; i < b.N; i++ { + _ = LogDist(aID, bID) + } +} + // The random tests is likely to miss the case where a and b are equal, // this test checks it explicitly. func TestID_logdistEqual(t *testing.T) { From b87581f2977393c842123d18c6e29e268f9b26cb Mon Sep 17 00:00:00 2001 From: cui Date: Mon, 13 Oct 2025 22:16:07 +0800 Subject: [PATCH 48/61] p2p/enode: optimize DistCmp (#32888) This speeds up DistCmp by 75% through using 64-bit operations instead of byte-wise XOR. --- p2p/enode/node.go | 7 ++++--- p2p/enode/node_test.go | 10 ++++++++++ 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/p2p/enode/node.go b/p2p/enode/node.go index 8198050353..dafde51d6a 100644 --- a/p2p/enode/node.go +++ b/p2p/enode/node.go @@ -359,9 +359,10 @@ func ParseID(in string) (ID, error) { // Returns -1 if a is closer to target, 1 if b is closer to target // and 0 if they are equal. func DistCmp(target, a, b ID) int { - for i := range target { - da := a[i] ^ target[i] - db := b[i] ^ target[i] + for i := 0; i < len(target); i += 8 { + tn := binary.BigEndian.Uint64(target[i : i+8]) + da := tn ^ binary.BigEndian.Uint64(a[i:i+8]) + db := tn ^ binary.BigEndian.Uint64(b[i:i+8]) if da > db { return 1 } else if da < db { diff --git a/p2p/enode/node_test.go b/p2p/enode/node_test.go index f276af6638..51bc4ebe15 100644 --- a/p2p/enode/node_test.go +++ b/p2p/enode/node_test.go @@ -368,6 +368,16 @@ func TestID_distcmpEqual(t *testing.T) { } } +func BenchmarkDistCmp(b *testing.B) { + base := ID{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15} + aID := ID{15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0} + bID := ID{15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 1} + b.ResetTimer() + for i := 0; i < b.N; i++ { + _ = DistCmp(base, aID, bID) + } +} + func TestID_logdist(t *testing.T) { logdistBig := func(a, b ID) int { abig, bbig := new(big.Int).SetBytes(a[:]), new(big.Int).SetBytes(b[:]) From 7b693ea17c9e5e950a36df29262fab7862ffda23 Mon Sep 17 00:00:00 2001 From: Marius van der Wijden Date: Mon, 13 Oct 2025 19:07:36 +0200 Subject: [PATCH 49/61] core/txpool/legacypool: move queue out of main txpool (#32270) This PR move the queue out of the main transaction pool. For now there should be no functional changes. I see this as a first step to refactor the legacypool and make the queue a fully separate concept from the main pending pool. --------- Signed-off-by: Csaba Kiraly Co-authored-by: Csaba Kiraly --- core/txpool/legacypool/legacypool.go | 235 +++++-------------- core/txpool/legacypool/legacypool_test.go | 67 +++--- core/txpool/legacypool/queue.go | 271 ++++++++++++++++++++++ 3 files changed, 367 insertions(+), 206 deletions(-) create mode 100644 core/txpool/legacypool/queue.go diff --git a/core/txpool/legacypool/legacypool.go b/core/txpool/legacypool/legacypool.go index e199d21c7a..b36d86dd19 100644 --- a/core/txpool/legacypool/legacypool.go +++ b/core/txpool/legacypool/legacypool.go @@ -23,7 +23,6 @@ import ( "math" "math/big" "slices" - "sort" "sync" "sync/atomic" "time" @@ -238,11 +237,10 @@ type LegacyPool struct { pendingNonces *noncer // Pending state tracking virtual nonces reserver txpool.Reserver // Address reserver to ensure exclusivity across subpools - pending map[common.Address]*list // All currently processable transactions - queue map[common.Address]*list // Queued but non-processable transactions - beats map[common.Address]time.Time // Last heartbeat from each known account - all *lookup // All transactions to allow lookups - priced *pricedList // All transactions sorted by price + pending map[common.Address]*list // All currently processable transactions + queue *queue + all *lookup // All transactions to allow lookups + priced *pricedList // All transactions sorted by price reqResetCh chan *txpoolResetRequest reqPromoteCh chan *accountSet @@ -266,14 +264,14 @@ func New(config Config, chain BlockChain) *LegacyPool { config = (&config).sanitize() // Create the transaction pool with its initial settings + signer := types.LatestSigner(chain.Config()) pool := &LegacyPool{ config: config, chain: chain, chainconfig: chain.Config(), - signer: types.LatestSigner(chain.Config()), + signer: signer, pending: make(map[common.Address]*list), - queue: make(map[common.Address]*list), - beats: make(map[common.Address]time.Time), + queue: newQueue(config, signer), all: newLookup(), reqResetCh: make(chan *txpoolResetRequest), reqPromoteCh: make(chan *accountSet), @@ -369,15 +367,9 @@ func (pool *LegacyPool) loop() { // Handle inactive account transaction eviction case <-evict.C: pool.mu.Lock() - for addr := range pool.queue { - // Any old enough should be removed - if time.Since(pool.beats[addr]) > pool.config.Lifetime { - list := pool.queue[addr].Flatten() - for _, tx := range list { - pool.removeTx(tx.Hash(), true, true) - } - queuedEvictionMeter.Mark(int64(len(list))) - } + evicted := pool.queue.evict(false) + for _, hash := range evicted { + pool.removeTx(hash, true, true) } pool.mu.Unlock() } @@ -459,11 +451,7 @@ func (pool *LegacyPool) stats() (int, int) { for _, list := range pool.pending { pending += list.Len() } - queued := 0 - for _, list := range pool.queue { - queued += list.Len() - } - return pending, queued + return pending, pool.queue.stats() } // Content retrieves the data content of the transaction pool, returning all the @@ -476,10 +464,7 @@ func (pool *LegacyPool) Content() (map[common.Address][]*types.Transaction, map[ for addr, list := range pool.pending { pending[addr] = list.Flatten() } - queued := make(map[common.Address][]*types.Transaction, len(pool.queue)) - for addr, list := range pool.queue { - queued[addr] = list.Flatten() - } + queued := pool.queue.content() return pending, queued } @@ -493,10 +478,7 @@ func (pool *LegacyPool) ContentFrom(addr common.Address) ([]*types.Transaction, if list, ok := pool.pending[addr]; ok { pending = list.Flatten() } - var queued []*types.Transaction - if list, ok := pool.queue[addr]; ok { - queued = list.Flatten() - } + queued := pool.queue.contentFrom(addr) return pending, queued } @@ -644,7 +626,7 @@ func (pool *LegacyPool) validateAuth(tx *types.Transaction) error { if pending := pool.pending[auth]; pending != nil { count += pending.Len() } - if queue := pool.queue[auth]; queue != nil { + if queue, ok := pool.queue.get(auth); ok { count += queue.Len() } if count > 1 { @@ -691,7 +673,7 @@ func (pool *LegacyPool) add(tx *types.Transaction) (replaced bool, err error) { // only by this subpool until all transactions are evicted var ( _, hasPending = pool.pending[from] - _, hasQueued = pool.queue[from] + _, hasQueued = pool.queue.get(from) ) if !hasPending && !hasQueued { if err := pool.reserver.Hold(from); err != nil { @@ -790,7 +772,7 @@ func (pool *LegacyPool) add(tx *types.Transaction) (replaced bool, err error) { log.Trace("Pooled new executable transaction", "hash", hash, "from", from, "to", tx.To()) // Successful promotion, bump the heartbeat - pool.beats[from] = time.Now() + pool.queue.bump(from) return old != nil, nil } // New transaction isn't replacing a pending one, push into queue @@ -815,7 +797,7 @@ func (pool *LegacyPool) isGapped(from common.Address, tx *types.Transaction) boo } // The transaction has a nonce gap with pending list, it's only considered // as executable if transactions in queue can fill up the nonce gap. - queue, ok := pool.queue[from] + queue, ok := pool.queue.get(from) if !ok { return true } @@ -831,25 +813,12 @@ func (pool *LegacyPool) isGapped(from common.Address, tx *types.Transaction) boo // // Note, this method assumes the pool lock is held! func (pool *LegacyPool) enqueueTx(hash common.Hash, tx *types.Transaction, addAll bool) (bool, error) { - // Try to insert the transaction into the future queue - from, _ := types.Sender(pool.signer, tx) // already validated - if pool.queue[from] == nil { - pool.queue[from] = newList(false) - } - inserted, old := pool.queue[from].Add(tx, pool.config.PriceBump) - if !inserted { - // An older transaction was better, discard this - queuedDiscardMeter.Mark(1) - return false, txpool.ErrReplaceUnderpriced + replaced, err := pool.queue.add(hash, tx) + if err != nil { + return false, err } - // Discard any previous transaction and mark this - if old != nil { - pool.all.Remove(old.Hash()) - pool.priced.Removed(1) - queuedReplaceMeter.Mark(1) - } else { - // Nothing was replaced, bump the queued counter - queuedGauge.Inc(1) + if replaced != nil { + pool.removeTx(*replaced, true, true) } // If the transaction isn't in lookup set but it's expected to be there, // show the error log. @@ -860,11 +829,7 @@ func (pool *LegacyPool) enqueueTx(hash common.Hash, tx *types.Transaction, addAl pool.all.Add(tx) pool.priced.Put(tx) } - // If we never record the heartbeat, do it right now. - if _, exist := pool.beats[from]; !exist { - pool.beats[from] = time.Now() - } - return old != nil, nil + return replaced != nil, nil } // promoteTx adds a transaction to the pending (processable) list of transactions @@ -899,7 +864,7 @@ func (pool *LegacyPool) promoteTx(addr common.Address, hash common.Hash, tx *typ pool.pendingNonces.set(addr, tx.Nonce()+1) // Successful promotion, bump the heartbeat - pool.beats[addr] = time.Now() + pool.queue.bump(addr) return true } @@ -1019,7 +984,7 @@ func (pool *LegacyPool) Status(hash common.Hash) txpool.TxStatus { if txList := pool.pending[from]; txList != nil && txList.txs.items[tx.Nonce()] != nil { return txpool.TxStatusPending - } else if txList := pool.queue[from]; txList != nil && txList.txs.items[tx.Nonce()] != nil { + } else if txList, ok := pool.queue.get(from); ok && txList.txs.items[tx.Nonce()] != nil { return txpool.TxStatusQueued } return txpool.TxStatusUnknown @@ -1096,7 +1061,7 @@ func (pool *LegacyPool) removeTx(hash common.Hash, outofbound bool, unreserve bo defer func() { var ( _, hasPending = pool.pending[addr] - _, hasQueued = pool.queue[addr] + _, hasQueued = pool.queue.get(addr) ) if !hasPending && !hasQueued { pool.reserver.Release(addr) @@ -1128,16 +1093,7 @@ func (pool *LegacyPool) removeTx(hash common.Hash, outofbound bool, unreserve bo } } // Transaction is in the future queue - if future := pool.queue[addr]; future != nil { - if removed, _ := future.Remove(tx); removed { - // Reduce the queued counter - queuedGauge.Dec(1) - } - if future.Empty() { - delete(pool.queue, addr) - delete(pool.beats, addr) - } - } + pool.queue.removeTx(addr, tx) return 0 } @@ -1285,10 +1241,7 @@ func (pool *LegacyPool) runReorg(done chan struct{}, reset *txpoolResetRequest, } } // Reset needs promote for all addresses - promoteAddrs = make([]common.Address, 0, len(pool.queue)) - for addr := range pool.queue { - promoteAddrs = append(promoteAddrs, addr) - } + promoteAddrs = append(promoteAddrs, pool.queue.addresses()...) } // Check for pending transactions for every account that sent new ones promoted := pool.promoteExecutables(promoteAddrs) @@ -1442,62 +1395,32 @@ func (pool *LegacyPool) reset(oldHead, newHead *types.Header) { // future queue to the set of pending transactions. During this process, all // invalidated transactions (low nonce, low balance) are deleted. func (pool *LegacyPool) promoteExecutables(accounts []common.Address) []*types.Transaction { - // Track the promoted transactions to broadcast them at once - var promoted []*types.Transaction - - // Iterate over all accounts and promote any executable transactions gasLimit := pool.currentHead.Load().GasLimit - for _, addr := range accounts { - list := pool.queue[addr] - if list == nil { - continue // Just in case someone calls with a non existing account - } - // Drop all transactions that are deemed too old (low nonce) - forwards := list.Forward(pool.currentState.GetNonce(addr)) - for _, tx := range forwards { - pool.all.Remove(tx.Hash()) - } - log.Trace("Removed old queued transactions", "count", len(forwards)) - // Drop all transactions that are too costly (low balance or out of gas) - drops, _ := list.Filter(pool.currentState.GetBalance(addr), gasLimit) - for _, tx := range drops { - pool.all.Remove(tx.Hash()) - } - log.Trace("Removed unpayable queued transactions", "count", len(drops)) - queuedNofundsMeter.Mark(int64(len(drops))) - - // Gather all executable transactions and promote them - readies := list.Ready(pool.pendingNonces.get(addr)) - for _, tx := range readies { - hash := tx.Hash() - if pool.promoteTx(addr, hash, tx) { - promoted = append(promoted, tx) - } + promotable, dropped, removedAddresses := pool.queue.promoteExecutables(accounts, gasLimit, pool.currentState, pool.pendingNonces) + promoted := make([]*types.Transaction, 0, len(promotable)) + + // promote all promoteable transactions + for _, tx := range promotable { + from, _ := pool.signer.Sender(tx) + if pool.promoteTx(from, tx.Hash(), tx) { + promoted = append(promoted, tx) } - log.Trace("Promoted queued transactions", "count", len(promoted)) - queuedGauge.Dec(int64(len(readies))) + } - // Drop all transactions over the allowed limit - var caps = list.Cap(int(pool.config.AccountQueue)) - for _, tx := range caps { - hash := tx.Hash() - pool.all.Remove(hash) - log.Trace("Removed cap-exceeding queued transaction", "hash", hash) - } - queuedRateLimitMeter.Mark(int64(len(caps))) - // Mark all the items dropped as removed - pool.priced.Removed(len(forwards) + len(drops) + len(caps)) - queuedGauge.Dec(int64(len(forwards) + len(drops) + len(caps))) + // remove all removable transactions + for _, hash := range dropped { + pool.all.Remove(hash) + } - // Delete the entire queue entry if it became empty. - if list.Empty() { - delete(pool.queue, addr) - delete(pool.beats, addr) - if _, ok := pool.pending[addr]; !ok { - pool.reserver.Release(addr) - } + // release all accounts that have no more transactions in the pool + for _, addr := range removedAddresses { + _, hasPending := pool.pending[addr] + _, hasQueued := pool.queue.get(addr) + if !hasPending && !hasQueued { + pool.reserver.Release(addr) } } + return promoted } @@ -1585,43 +1508,17 @@ func (pool *LegacyPool) truncatePending() { // truncateQueue drops the oldest transactions in the queue if the pool is above the global queue limit. func (pool *LegacyPool) truncateQueue() { - queued := uint64(0) - for _, list := range pool.queue { - queued += uint64(list.Len()) - } - if queued <= pool.config.GlobalQueue { - return - } + removed, removedAddresses := pool.queue.truncate() - // Sort all accounts with queued transactions by heartbeat - addresses := make(addressesByHeartbeat, 0, len(pool.queue)) - for addr := range pool.queue { - addresses = append(addresses, addressByHeartbeat{addr, pool.beats[addr]}) + // remove all removable transactions + for _, hash := range removed { + pool.all.Remove(hash) } - sort.Sort(sort.Reverse(addresses)) - - // Drop transactions until the total is below the limit - for drop := queued - pool.config.GlobalQueue; drop > 0 && len(addresses) > 0; { - addr := addresses[len(addresses)-1] - list := pool.queue[addr.address] - addresses = addresses[:len(addresses)-1] - - // Drop all transactions if they are less than the overflow - if size := uint64(list.Len()); size <= drop { - for _, tx := range list.Flatten() { - pool.removeTx(tx.Hash(), true, true) - } - drop -= size - queuedRateLimitMeter.Mark(int64(size)) - continue - } - // Otherwise drop only last few transactions - txs := list.Flatten() - for i := len(txs) - 1; i >= 0 && drop > 0; i-- { - pool.removeTx(txs[i].Hash(), true, true) - drop-- - queuedRateLimitMeter.Mark(1) + for _, addr := range removedAddresses { + _, hasPending := pool.pending[addr] + if !hasPending { + pool.reserver.Release(addr) } } } @@ -1679,25 +1576,13 @@ func (pool *LegacyPool) demoteUnexecutables() { // Delete the entire pending entry if it became empty. if list.Empty() { delete(pool.pending, addr) - if _, ok := pool.queue[addr]; !ok { + if _, ok := pool.queue.get(addr); !ok { pool.reserver.Release(addr) } } } } -// addressByHeartbeat is an account address tagged with its last activity timestamp. -type addressByHeartbeat struct { - address common.Address - heartbeat time.Time -} - -type addressesByHeartbeat []addressByHeartbeat - -func (a addressesByHeartbeat) Len() int { return len(a) } -func (a addressesByHeartbeat) Less(i, j int) bool { return a[i].heartbeat.Before(a[j].heartbeat) } -func (a addressesByHeartbeat) Swap(i, j int) { a[i], a[j] = a[j], a[i] } - // accountSet is simply a set of addresses to check for existence, and a signer // capable of deriving addresses from transactions. type accountSet struct { @@ -1938,17 +1823,17 @@ func (pool *LegacyPool) Clear() { // acquire the subpool lock until the transaction addition is completed. for addr := range pool.pending { - if _, ok := pool.queue[addr]; !ok { + if _, ok := pool.queue.get(addr); !ok { pool.reserver.Release(addr) } } - for addr := range pool.queue { + for _, addr := range pool.queue.addresses() { pool.reserver.Release(addr) } pool.all.Clear() pool.priced.Reheap() pool.pending = make(map[common.Address]*list) - pool.queue = make(map[common.Address]*list) + pool.queue = newQueue(pool.config, pool.signer) pool.pendingNonces = newNoncer(pool.currentState) } diff --git a/core/txpool/legacypool/legacypool_test.go b/core/txpool/legacypool/legacypool_test.go index 0c8642659d..fb994d8208 100644 --- a/core/txpool/legacypool/legacypool_test.go +++ b/core/txpool/legacypool/legacypool_test.go @@ -466,8 +466,8 @@ func TestQueue(t *testing.T) { if _, ok := pool.pending[from].txs.items[tx.Nonce()]; ok { t.Error("expected transaction to be in tx pool") } - if len(pool.queue) > 0 { - t.Error("expected transaction queue to be empty. is", len(pool.queue)) + if len(pool.queue.queued) > 0 { + t.Error("expected transaction queue to be empty. is", len(pool.queue.queued)) } } @@ -492,8 +492,8 @@ func TestQueue2(t *testing.T) { if len(pool.pending) != 1 { t.Error("expected pending length to be 1, got", len(pool.pending)) } - if pool.queue[from].Len() != 2 { - t.Error("expected len(queue) == 2, got", pool.queue[from].Len()) + if list, _ := pool.queue.get(from); list.Len() != 2 { + t.Error("expected len(queue) == 2, got", list.Len()) } } @@ -639,8 +639,8 @@ func TestMissingNonce(t *testing.T) { if len(pool.pending) != 0 { t.Error("expected 0 pending transactions, got", len(pool.pending)) } - if pool.queue[addr].Len() != 1 { - t.Error("expected 1 queued transaction, got", pool.queue[addr].Len()) + if list, _ := pool.queue.get(addr); list.Len() != 1 { + t.Error("expected 1 queued transaction, got", list.Len()) } if pool.all.Count() != 1 { t.Error("expected 1 total transactions, got", pool.all.Count()) @@ -712,8 +712,8 @@ func TestDropping(t *testing.T) { if pool.pending[account].Len() != 3 { t.Errorf("pending transaction mismatch: have %d, want %d", pool.pending[account].Len(), 3) } - if pool.queue[account].Len() != 3 { - t.Errorf("queued transaction mismatch: have %d, want %d", pool.queue[account].Len(), 3) + if list, _ := pool.queue.get(account); list.Len() != 3 { + t.Errorf("queued transaction mismatch: have %d, want %d", list.Len(), 3) } if pool.all.Count() != 6 { t.Errorf("total transaction mismatch: have %d, want %d", pool.all.Count(), 6) @@ -722,8 +722,8 @@ func TestDropping(t *testing.T) { if pool.pending[account].Len() != 3 { t.Errorf("pending transaction mismatch: have %d, want %d", pool.pending[account].Len(), 3) } - if pool.queue[account].Len() != 3 { - t.Errorf("queued transaction mismatch: have %d, want %d", pool.queue[account].Len(), 3) + if list, _ := pool.queue.get(account); list.Len() != 3 { + t.Errorf("queued transaction mismatch: have %d, want %d", list.Len(), 3) } if pool.all.Count() != 6 { t.Errorf("total transaction mismatch: have %d, want %d", pool.all.Count(), 6) @@ -741,13 +741,14 @@ func TestDropping(t *testing.T) { if _, ok := pool.pending[account].txs.items[tx2.Nonce()]; ok { t.Errorf("out-of-fund pending transaction present: %v", tx1) } - if _, ok := pool.queue[account].txs.items[tx10.Nonce()]; !ok { + list, _ := pool.queue.get(account) + if _, ok := list.txs.items[tx10.Nonce()]; !ok { t.Errorf("funded queued transaction missing: %v", tx10) } - if _, ok := pool.queue[account].txs.items[tx11.Nonce()]; !ok { + if _, ok := list.txs.items[tx11.Nonce()]; !ok { t.Errorf("funded queued transaction missing: %v", tx10) } - if _, ok := pool.queue[account].txs.items[tx12.Nonce()]; ok { + if _, ok := list.txs.items[tx12.Nonce()]; ok { t.Errorf("out-of-fund queued transaction present: %v", tx11) } if pool.all.Count() != 4 { @@ -763,10 +764,11 @@ func TestDropping(t *testing.T) { if _, ok := pool.pending[account].txs.items[tx1.Nonce()]; ok { t.Errorf("over-gased pending transaction present: %v", tx1) } - if _, ok := pool.queue[account].txs.items[tx10.Nonce()]; !ok { + list, _ = pool.queue.get(account) + if _, ok := list.txs.items[tx10.Nonce()]; !ok { t.Errorf("funded queued transaction missing: %v", tx10) } - if _, ok := pool.queue[account].txs.items[tx11.Nonce()]; ok { + if _, ok := list.txs.items[tx11.Nonce()]; ok { t.Errorf("over-gased queued transaction present: %v", tx11) } if pool.all.Count() != 2 { @@ -820,8 +822,8 @@ func TestPostponing(t *testing.T) { if pending := pool.pending[accs[0]].Len() + pool.pending[accs[1]].Len(); pending != len(txs) { t.Errorf("pending transaction mismatch: have %d, want %d", pending, len(txs)) } - if len(pool.queue) != 0 { - t.Errorf("queued accounts mismatch: have %d, want %d", len(pool.queue), 0) + if len(pool.queue.addresses()) != 0 { + t.Errorf("queued accounts mismatch: have %d, want %d", len(pool.queue.addresses()), 0) } if pool.all.Count() != len(txs) { t.Errorf("total transaction mismatch: have %d, want %d", pool.all.Count(), len(txs)) @@ -830,8 +832,8 @@ func TestPostponing(t *testing.T) { if pending := pool.pending[accs[0]].Len() + pool.pending[accs[1]].Len(); pending != len(txs) { t.Errorf("pending transaction mismatch: have %d, want %d", pending, len(txs)) } - if len(pool.queue) != 0 { - t.Errorf("queued accounts mismatch: have %d, want %d", len(pool.queue), 0) + if len(pool.queue.addresses()) != 0 { + t.Errorf("queued accounts mismatch: have %d, want %d", len(pool.queue.addresses()), 0) } if pool.all.Count() != len(txs) { t.Errorf("total transaction mismatch: have %d, want %d", pool.all.Count(), len(txs)) @@ -847,7 +849,8 @@ func TestPostponing(t *testing.T) { if _, ok := pool.pending[accs[0]].txs.items[txs[0].Nonce()]; !ok { t.Errorf("tx %d: valid and funded transaction missing from pending pool: %v", 0, txs[0]) } - if _, ok := pool.queue[accs[0]].txs.items[txs[0].Nonce()]; ok { + list, _ := pool.queue.get(accs[0]) + if _, ok := list.txs.items[txs[0].Nonce()]; ok { t.Errorf("tx %d: valid and funded transaction present in future queue: %v", 0, txs[0]) } for i, tx := range txs[1:100] { @@ -855,14 +858,14 @@ func TestPostponing(t *testing.T) { if _, ok := pool.pending[accs[0]].txs.items[tx.Nonce()]; ok { t.Errorf("tx %d: valid but future transaction present in pending pool: %v", i+1, tx) } - if _, ok := pool.queue[accs[0]].txs.items[tx.Nonce()]; !ok { + if _, ok := list.txs.items[tx.Nonce()]; !ok { t.Errorf("tx %d: valid but future transaction missing from future queue: %v", i+1, tx) } } else { if _, ok := pool.pending[accs[0]].txs.items[tx.Nonce()]; ok { t.Errorf("tx %d: out-of-fund transaction present in pending pool: %v", i+1, tx) } - if _, ok := pool.queue[accs[0]].txs.items[tx.Nonce()]; ok { + if _, ok := list.txs.items[tx.Nonce()]; ok { t.Errorf("tx %d: out-of-fund transaction present in future queue: %v", i+1, tx) } } @@ -872,13 +875,14 @@ func TestPostponing(t *testing.T) { if pool.pending[accs[1]] != nil { t.Errorf("invalidated account still has pending transactions") } + list, _ = pool.queue.get(accs[1]) for i, tx := range txs[100:] { if i%2 == 1 { - if _, ok := pool.queue[accs[1]].txs.items[tx.Nonce()]; !ok { + if _, ok := list.txs.items[tx.Nonce()]; !ok { t.Errorf("tx %d: valid but future transaction missing from future queue: %v", 100+i, tx) } } else { - if _, ok := pool.queue[accs[1]].txs.items[tx.Nonce()]; ok { + if _, ok := list.txs.items[tx.Nonce()]; ok { t.Errorf("tx %d: out-of-fund transaction present in future queue: %v", 100+i, tx) } } @@ -963,13 +967,14 @@ func TestQueueAccountLimiting(t *testing.T) { if len(pool.pending) != 0 { t.Errorf("tx %d: pending pool size mismatch: have %d, want %d", i, len(pool.pending), 0) } + list, _ := pool.queue.get(account) if i <= testTxPoolConfig.AccountQueue { - if pool.queue[account].Len() != int(i) { - t.Errorf("tx %d: queue size mismatch: have %d, want %d", i, pool.queue[account].Len(), i) + if list.Len() != int(i) { + t.Errorf("tx %d: queue size mismatch: have %d, want %d", i, list.Len(), i) } } else { - if pool.queue[account].Len() != int(testTxPoolConfig.AccountQueue) { - t.Errorf("tx %d: queue limit mismatch: have %d, want %d", i, pool.queue[account].Len(), testTxPoolConfig.AccountQueue) + if list.Len() != int(testTxPoolConfig.AccountQueue) { + t.Errorf("tx %d: queue limit mismatch: have %d, want %d", i, list.Len(), testTxPoolConfig.AccountQueue) } } } @@ -1020,7 +1025,7 @@ func TestQueueGlobalLimiting(t *testing.T) { pool.addRemotesSync(txs) queued := 0 - for addr, list := range pool.queue { + for addr, list := range pool.queue.queued { if list.Len() > int(config.AccountQueue) { t.Errorf("addr %x: queued accounts overflown allowance: %d > %d", addr, list.Len(), config.AccountQueue) } @@ -1179,8 +1184,8 @@ func TestPendingLimiting(t *testing.T) { if pool.pending[account].Len() != int(i)+1 { t.Errorf("tx %d: pending pool size mismatch: have %d, want %d", i, pool.pending[account].Len(), i+1) } - if len(pool.queue) != 0 { - t.Errorf("tx %d: queue size mismatch: have %d, want %d", i, pool.queue[account].Len(), 0) + if len(pool.queue.addresses()) != 0 { + t.Errorf("tx %d: queue size mismatch: have %d, want %d", i, len(pool.queue.addresses()), 0) } } if pool.all.Count() != int(testTxPoolConfig.AccountQueue+5) { diff --git a/core/txpool/legacypool/queue.go b/core/txpool/legacypool/queue.go new file mode 100644 index 0000000000..b8417064f7 --- /dev/null +++ b/core/txpool/legacypool/queue.go @@ -0,0 +1,271 @@ +// 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 legacypool + +import ( + "sort" + "time" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/state" + "github.com/ethereum/go-ethereum/core/txpool" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/log" +) + +type queue struct { + config Config + signer types.Signer + queued map[common.Address]*list // Queued but non-processable transactions + beats map[common.Address]time.Time // Last heartbeat from each known account +} + +func newQueue(config Config, signer types.Signer) *queue { + return &queue{ + signer: signer, + config: config, + queued: make(map[common.Address]*list), + beats: make(map[common.Address]time.Time), + } +} + +func (q *queue) evict(force bool) []common.Hash { + removed := make([]common.Hash, 0) + for addr, list := range q.queued { + // Any transactions old enough should be removed + if force || time.Since(q.beats[addr]) > q.config.Lifetime { + list := list.Flatten() + for _, tx := range list { + q.removeTx(addr, tx) + removed = append(removed, tx.Hash()) + } + queuedEvictionMeter.Mark(int64(len(list))) + } + } + return removed +} + +func (q *queue) stats() int { + queued := 0 + for _, list := range q.queued { + queued += list.Len() + } + return queued +} + +func (q *queue) content() map[common.Address][]*types.Transaction { + queued := make(map[common.Address][]*types.Transaction, len(q.queued)) + for addr, list := range q.queued { + queued[addr] = list.Flatten() + } + return queued +} + +func (q *queue) contentFrom(addr common.Address) []*types.Transaction { + var queued []*types.Transaction + if list, ok := q.get(addr); ok { + queued = list.Flatten() + } + return queued +} + +func (q *queue) get(addr common.Address) (*list, bool) { + l, ok := q.queued[addr] + return l, ok +} + +func (q *queue) bump(addr common.Address) { + q.beats[addr] = time.Now() +} + +func (q *queue) addresses() []common.Address { + addrs := make([]common.Address, 0, len(q.queued)) + for addr := range q.queued { + addrs = append(addrs, addr) + } + return addrs +} + +func (q queue) removeTx(addr common.Address, tx *types.Transaction) { + if future := q.queued[addr]; future != nil { + if txOld := future.txs.Get(tx.Nonce()); txOld != nil && txOld.Hash() != tx.Hash() { + // Edge case, a different transaction + // with the same nonce is in the queued, just ignore + return + } + if removed, _ := future.Remove(tx); removed { + // Reduce the queued counter + queuedGauge.Dec(1) + } + if future.Empty() { + delete(q.queued, addr) + delete(q.beats, addr) + } + } +} + +func (q *queue) add(hash common.Hash, tx *types.Transaction) (*common.Hash, error) { + // Try to insert the transaction into the future queue + from, _ := types.Sender(q.signer, tx) // already validated + if q.queued[from] == nil { + q.queued[from] = newList(false) + } + inserted, old := q.queued[from].Add(tx, q.config.PriceBump) + if !inserted { + // An older transaction was better, discard this + queuedDiscardMeter.Mark(1) + return nil, txpool.ErrReplaceUnderpriced + } + // If we never record the heartbeat, do it right now. + if _, exist := q.beats[from]; !exist { + q.beats[from] = time.Now() + } + if old == nil { + // Nothing was replaced, bump the queued counter + queuedGauge.Inc(1) + return nil, nil + } + h := old.Hash() + // Transaction was replaced, bump the replacement counter + queuedReplaceMeter.Mark(1) + return &h, nil +} + +// promoteExecutables iterates over all accounts with queued transactions, selecting +// for promotion any that are now executable. It also drops any transactions that are +// deemed too old (nonce too low) or too costly (insufficient funds or over gas limit). +// +// Returns three lists: all transactions that were removed from the queue and selected +// for promotion; all other transactions that were removed from the queue and dropped; +// the list of addresses removed. +func (q *queue) promoteExecutables(accounts []common.Address, gasLimit uint64, currentState *state.StateDB, nonces *noncer) ([]*types.Transaction, []common.Hash, []common.Address) { + // Track the promotable transactions to broadcast them at once + var promotable []*types.Transaction + var dropped []common.Hash + var removedAddresses []common.Address + + // Iterate over all accounts and promote any executable transactions + for _, addr := range accounts { + list := q.queued[addr] + if list == nil { + continue // Just in case someone calls with a non existing account + } + // Drop all transactions that are deemed too old (low nonce) + forwards := list.Forward(currentState.GetNonce(addr)) + for _, tx := range forwards { + dropped = append(dropped, tx.Hash()) + } + log.Trace("Removing old queued transactions", "count", len(forwards)) + // Drop all transactions that are too costly (low balance or out of gas) + drops, _ := list.Filter(currentState.GetBalance(addr), gasLimit) + for _, tx := range drops { + dropped = append(dropped, tx.Hash()) + } + log.Trace("Removing unpayable queued transactions", "count", len(drops)) + queuedNofundsMeter.Mark(int64(len(drops))) + + // Gather all executable transactions and promote them + readies := list.Ready(nonces.get(addr)) + promotable = append(promotable, readies...) + log.Trace("Promoting queued transactions", "count", len(promotable)) + queuedGauge.Dec(int64(len(readies))) + + // Drop all transactions over the allowed limit + var caps = list.Cap(int(q.config.AccountQueue)) + for _, tx := range caps { + hash := tx.Hash() + dropped = append(dropped, hash) + log.Trace("Removing cap-exceeding queued transaction", "hash", hash) + } + queuedRateLimitMeter.Mark(int64(len(caps))) + + // Delete the entire queue entry if it became empty. + if list.Empty() { + delete(q.queued, addr) + delete(q.beats, addr) + removedAddresses = append(removedAddresses, addr) + } + } + queuedGauge.Dec(int64(len(dropped))) + return promotable, dropped, removedAddresses +} + +// truncate drops the oldest transactions from the queue until the total +// number is below the configured limit. +// Returns the hashes of all dropped transactions, and the addresses of +// accounts that became empty due to the truncation. +func (q *queue) truncate() ([]common.Hash, []common.Address) { + queued := uint64(0) + for _, list := range q.queued { + queued += uint64(list.Len()) + } + if queued <= q.config.GlobalQueue { + return nil, nil + } + + // Sort all accounts with queued transactions by heartbeat + addresses := make(addressesByHeartbeat, 0, len(q.queued)) + for addr := range q.queued { + addresses = append(addresses, addressByHeartbeat{addr, q.beats[addr]}) + } + sort.Sort(sort.Reverse(addresses)) + removed := make([]common.Hash, 0) + removedAddresses := make([]common.Address, 0) + + // Drop transactions until the total is below the limit + for drop := queued - q.config.GlobalQueue; drop > 0 && len(addresses) > 0; { + addr := addresses[len(addresses)-1] + list := q.queued[addr.address] + + addresses = addresses[:len(addresses)-1] + + // Drop all transactions if they are less than the overflow + if size := uint64(list.Len()); size <= drop { + for _, tx := range list.Flatten() { + q.removeTx(addr.address, tx) + removed = append(removed, tx.Hash()) + } + drop -= size + queuedRateLimitMeter.Mark(int64(size)) + removedAddresses = append(removedAddresses, addr.address) + continue + } + // Otherwise drop only last few transactions + txs := list.Flatten() + for i := len(txs) - 1; i >= 0 && drop > 0; i-- { + q.removeTx(addr.address, txs[i]) + removed = append(removed, txs[i].Hash()) + drop-- + queuedRateLimitMeter.Mark(1) + } + } + + // no need to clear empty accounts, removeTx already does that + return removed, removedAddresses +} + +// addressByHeartbeat is an account address tagged with its last activity timestamp. +type addressByHeartbeat struct { + address common.Address + heartbeat time.Time +} + +type addressesByHeartbeat []addressByHeartbeat + +func (a addressesByHeartbeat) Len() int { return len(a) } +func (a addressesByHeartbeat) Less(i, j int) bool { return a[i].heartbeat.Before(a[j].heartbeat) } +func (a addressesByHeartbeat) Swap(i, j int) { a[i], a[j] = a[j], a[i] } From b28241ba85a294ad0f860390943170329c37a53b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felf=C3=B6ldi=20Zsolt?= Date: Mon, 13 Oct 2025 19:21:01 +0200 Subject: [PATCH 50/61] cmd/workload: filter fuzzer test (#31613) This PR adds a `filterfuzz` subcommand to the workload tester that generates requests similarly to `filtergen` (though with a much smaller block length limit) and also verifies the results by retrieving all block receipts in the range and locally filtering out relevant results. Unlike `filtergen` that operates on the finalized chain range only, `filterfuzz` does check the head region, actually it seeds a new query at every new chain head. --- cmd/workload/filtertest.go | 15 +- cmd/workload/filtertestfuzz.go | 337 +++++++++++++++++++++++++++++++++ cmd/workload/filtertestgen.go | 59 +++--- cmd/workload/main.go | 1 + 4 files changed, 378 insertions(+), 34 deletions(-) create mode 100644 cmd/workload/filtertestfuzz.go diff --git a/cmd/workload/filtertest.go b/cmd/workload/filtertest.go index 9f0b6cab44..d77cbc5768 100644 --- a/cmd/workload/filtertest.go +++ b/cmd/workload/filtertest.go @@ -182,13 +182,14 @@ func (s *filterTestSuite) loadQueries() error { // filterQuery is a single query for testing. type filterQuery struct { - FromBlock int64 `json:"fromBlock"` - ToBlock int64 `json:"toBlock"` - Address []common.Address `json:"address"` - Topics [][]common.Hash `json:"topics"` - ResultHash *common.Hash `json:"resultHash,omitempty"` - results []types.Log - Err error `json:"error,omitempty"` + FromBlock int64 `json:"fromBlock"` + ToBlock int64 `json:"toBlock"` + lastBlockHash common.Hash + Address []common.Address `json:"address"` + Topics [][]common.Hash `json:"topics"` + ResultHash *common.Hash `json:"resultHash,omitempty"` + results []types.Log + Err error `json:"error,omitempty"` } func (fq *filterQuery) isWildcard() bool { diff --git a/cmd/workload/filtertestfuzz.go b/cmd/workload/filtertestfuzz.go new file mode 100644 index 0000000000..3549f4db56 --- /dev/null +++ b/cmd/workload/filtertestfuzz.go @@ -0,0 +1,337 @@ +// Copyright 2025 The go-ethereum Authors +// This file is part of go-ethereum. +// +// go-ethereum is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// go-ethereum 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 General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with go-ethereum. If not, see . + +package main + +import ( + "context" + "fmt" + "math/big" + "reflect" + "slices" + "time" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/lru" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/rpc" + "github.com/urfave/cli/v2" +) + +const maxFilterRangeForTestFuzz = 300 + +var ( + filterFuzzCommand = &cli.Command{ + Name: "filterfuzz", + Usage: "Generates queries and compares results against matches derived from receipts", + ArgsUsage: "", + Action: filterFuzzCmd, + Flags: []cli.Flag{}, + } +) + +// filterFuzzCmd is the main function of the filter fuzzer. +func filterFuzzCmd(ctx *cli.Context) error { + f := newFilterTestGen(ctx, maxFilterRangeForTestFuzz) + var lastHead *types.Header + headerCache := lru.NewCache[common.Hash, *types.Header](200) + + commonAncestor := func(oldPtr, newPtr *types.Header) *types.Header { + if oldPtr == nil || newPtr == nil { + return nil + } + if newPtr.Number.Uint64() > oldPtr.Number.Uint64()+100 || oldPtr.Number.Uint64() > newPtr.Number.Uint64()+100 { + return nil + } + for oldPtr.Hash() != newPtr.Hash() { + if newPtr.Number.Uint64() >= oldPtr.Number.Uint64() { + if parent, _ := headerCache.Get(newPtr.ParentHash); parent != nil { + newPtr = parent + } else { + newPtr, _ = getHeaderByHash(f.client, newPtr.ParentHash) + if newPtr == nil { + return nil + } + headerCache.Add(newPtr.Hash(), newPtr) + } + } + if oldPtr.Number.Uint64() > newPtr.Number.Uint64() { + oldPtr, _ = headerCache.Get(oldPtr.ParentHash) + if oldPtr == nil { + return nil + } + } + } + return newPtr + } + + fetchHead := func() (*types.Header, bool) { + currentHead, err := getLatestHeader(f.client) + if err != nil { + fmt.Println("Could not fetch head block", err) + return nil, false + } + headerCache.Add(currentHead.Hash(), currentHead) + if lastHead != nil && currentHead.Hash() == lastHead.Hash() { + return currentHead, false + } + f.blockLimit = currentHead.Number.Int64() + ca := commonAncestor(lastHead, currentHead) + fmt.Print("*** New head ", f.blockLimit) + if ca == nil { + fmt.Println(" ") + } else { + if reorged := lastHead.Number.Uint64() - ca.Number.Uint64(); reorged > 0 { + fmt.Print(" reorged ", reorged) + } + if missed := currentHead.Number.Uint64() - ca.Number.Uint64() - 1; missed > 0 { + fmt.Print(" missed ", missed) + } + fmt.Println() + } + lastHead = currentHead + return currentHead, true + } + + tryExtendQuery := func(query *filterQuery) *filterQuery { + for { + extQuery := f.extendRange(query) + if extQuery == nil { + return query + } + extQuery.checkLastBlockHash(f.client) + extQuery.run(f.client, nil) + if extQuery.Err == nil && len(extQuery.results) == 0 { + // query is useless now due to major reorg; abandon and continue + fmt.Println("Zero length results") + return nil + } + if extQuery.Err != nil { + extQuery.printError() + return nil + } + if len(extQuery.results) > maxFilterResultSize { + return query + } + query = extQuery + } + } + + var ( + mmQuery *filterQuery + mmRetry, mmNextRetry int + ) + +mainLoop: + for { + select { + case <-ctx.Done(): + return nil + default: + } + var query *filterQuery + if mmQuery != nil { + if mmRetry == 0 { + query = mmQuery + mmRetry = mmNextRetry + mmNextRetry *= 2 + query.checkLastBlockHash(f.client) + query.run(f.client, nil) + if query.Err != nil { + query.printError() + continue + } + fmt.Println("Retrying query from:", query.FromBlock, "to:", query.ToBlock, "results:", len(query.results)) + } else { + mmRetry-- + } + } + if query == nil { + currentHead, isNewHead := fetchHead() + if currentHead == nil { + select { + case <-ctx.Done(): + return nil + case <-time.After(time.Second): + } + continue mainLoop + } + if isNewHead { + query = f.newHeadSeedQuery(currentHead.Number.Int64()) + } else { + query = f.newQuery() + } + query.checkLastBlockHash(f.client) + query.run(f.client, nil) + if query.Err != nil { + query.printError() + continue + } + fmt.Println("New query from:", query.FromBlock, "to:", query.ToBlock, "results:", len(query.results)) + if len(query.results) == 0 || len(query.results) > maxFilterResultSize { + continue mainLoop + } + if query = tryExtendQuery(query); query == nil { + continue mainLoop + } + } + if !query.checkLastBlockHash(f.client) { + fmt.Println("Reorg during search") + continue mainLoop + } + // now we have a new query; check results + results, err := query.getResultsFromReceipts(f.client) + if err != nil { + fmt.Println("Could not fetch results from receipts", err) + continue mainLoop + } + if !query.checkLastBlockHash(f.client) { + fmt.Println("Reorg during search") + continue mainLoop + } + if !reflect.DeepEqual(query.results, results) { + fmt.Println("Results mismatch from:", query.FromBlock, "to:", query.ToBlock, "addresses:", query.Address, "topics:", query.Topics) + resShared, resGetLogs, resReceipts := compareResults(query.results, results) + fmt.Println(" shared:", len(resShared)) + fmt.Println(" only from getLogs:", len(resGetLogs), resGetLogs) + fmt.Println(" only from receipts:", len(resReceipts), resReceipts) + if mmQuery != query { + mmQuery = query + mmRetry = 0 + mmNextRetry = 1 + } + continue mainLoop + } + fmt.Println("Successful query from:", query.FromBlock, "to:", query.ToBlock, "results:", len(query.results)) + f.storeQuery(query) + } +} + +func compareResults(a, b []types.Log) (shared, onlya, onlyb []types.Log) { + for len(a) > 0 && len(b) > 0 { + if reflect.DeepEqual(a[0], b[0]) { + shared = append(shared, a[0]) + a = a[1:] + b = b[1:] + } else { + for i := 1; ; i++ { + if i >= len(a) { // b[0] not found in a + onlyb = append(onlyb, b[0]) + b = b[1:] + break + } + if i >= len(b) { // a[0] not found in b + onlya = append(onlya, a[0]) + a = a[1:] + break + } + if reflect.DeepEqual(b[0], a[i]) { // a[:i] not found in b + onlya = append(onlya, a[:i]...) + a = a[i:] + break + } + if reflect.DeepEqual(a[0], b[i]) { // b[:i] not found in a + onlyb = append(onlyb, b[:i]...) + b = b[i:] + break + } + } + } + } + onlya = append(onlya, a...) + onlyb = append(onlyb, b...) + return +} + +func getLatestHeader(client *client) (*types.Header, error) { + ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) + defer cancel() + + return client.Eth.HeaderByNumber(ctx, big.NewInt(int64(rpc.LatestBlockNumber))) +} + +func getHeaderByHash(client *client, hash common.Hash) (*types.Header, error) { + ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) + defer cancel() + + return client.Eth.HeaderByHash(ctx, hash) +} + +// newHeadSeedQuery creates a query that gets all logs from the latest head. +func (s *filterTestGen) newHeadSeedQuery(head int64) *filterQuery { + return &filterQuery{ + FromBlock: head, + ToBlock: head, + } +} + +func (fq *filterQuery) checkLastBlockHash(client *client) bool { + ctx, cancel := context.WithTimeout(context.Background(), time.Second*30) + defer cancel() + + header, err := client.Eth.HeaderByNumber(ctx, big.NewInt(fq.ToBlock)) + if err != nil { + fmt.Println("Cound not fetch last block hash of query number:", fq.ToBlock, "error:", err) + fq.lastBlockHash = common.Hash{} + return false + } + hash := header.Hash() + if fq.lastBlockHash == hash { + return true + } + fq.lastBlockHash = hash + return false +} + +func (fq *filterQuery) filterLog(log *types.Log) bool { + if len(fq.Address) > 0 && !slices.Contains(fq.Address, log.Address) { + return false + } + // If the to filtered topics is greater than the amount of topics in logs, skip. + if len(fq.Topics) > len(log.Topics) { + return false + } + for i, sub := range fq.Topics { + if len(sub) == 0 { + continue // empty rule set == wildcard + } + if !slices.Contains(sub, log.Topics[i]) { + return false + } + } + return true +} + +func (fq *filterQuery) getResultsFromReceipts(client *client) ([]types.Log, error) { + ctx, cancel := context.WithTimeout(context.Background(), time.Second*30) + defer cancel() + + var results []types.Log + for blockNumber := fq.FromBlock; blockNumber <= fq.ToBlock; blockNumber++ { + receipts, err := client.Eth.BlockReceipts(ctx, rpc.BlockNumberOrHashWithNumber(rpc.BlockNumber(blockNumber))) + if err != nil { + return nil, err + } + for _, receipt := range receipts { + for _, log := range receipt.Logs { + if fq.filterLog(log) { + results = append(results, *log) + } + } + } + } + return results, nil +} diff --git a/cmd/workload/filtertestgen.go b/cmd/workload/filtertestgen.go index 6d1f639819..603e3dea67 100644 --- a/cmd/workload/filtertestgen.go +++ b/cmd/workload/filtertestgen.go @@ -32,6 +32,17 @@ import ( "github.com/urfave/cli/v2" ) +const ( + // Parameter of the random filter query generator. + maxFilterRangeForTestGen = 100000000000 + maxFilterResultSize = 1000 + filterBuckets = 10 + maxFilterBucketSize = 100 + filterSeedChance = 10 + filterMergeChance = 45 + filterExtendChance = 50 +) + var ( filterGenerateCommand = &cli.Command{ Name: "filtergen", @@ -58,7 +69,7 @@ var ( // filterGenCmd is the main function of the filter tests generator. func filterGenCmd(ctx *cli.Context) error { - f := newFilterTestGen(ctx) + f := newFilterTestGen(ctx, maxFilterRangeForTestGen) lastWrite := time.Now() for { select { @@ -67,7 +78,7 @@ func filterGenCmd(ctx *cli.Context) error { default: } - f.updateFinalizedBlock() + f.setLimitToFinalizedBlock() query := f.newQuery() query.run(f.client, nil) if query.Err != nil { @@ -75,7 +86,7 @@ func filterGenCmd(ctx *cli.Context) error { exit("filter query failed") } if len(query.results) > 0 && len(query.results) <= maxFilterResultSize { - for { + for rand.Intn(100) < filterExtendChance { extQuery := f.extendRange(query) if extQuery == nil { break @@ -108,39 +119,32 @@ func filterGenCmd(ctx *cli.Context) error { // filterTestGen is the filter query test generator. type filterTestGen struct { - client *client - queryFile string + client *client + queryFile string + maxFilterRange int64 - finalizedBlock int64 - queries [filterBuckets][]*filterQuery + blockLimit int64 + queries [filterBuckets][]*filterQuery } -func newFilterTestGen(ctx *cli.Context) *filterTestGen { +func newFilterTestGen(ctx *cli.Context, maxFilterRange int64) *filterTestGen { return &filterTestGen{ - client: makeClient(ctx), - queryFile: ctx.String(filterQueryFileFlag.Name), + client: makeClient(ctx), + queryFile: ctx.String(filterQueryFileFlag.Name), + maxFilterRange: maxFilterRange, } } -func (s *filterTestGen) updateFinalizedBlock() { - s.finalizedBlock = mustGetFinalizedBlock(s.client) +func (s *filterTestGen) setLimitToFinalizedBlock() { + s.blockLimit = mustGetFinalizedBlock(s.client) } -const ( - // Parameter of the random filter query generator. - maxFilterRange = 10000000 - maxFilterResultSize = 300 - filterBuckets = 10 - maxFilterBucketSize = 100 - filterSeedChance = 10 - filterMergeChance = 45 -) - // storeQuery adds a filter query to the output file. func (s *filterTestGen) storeQuery(query *filterQuery) { query.ResultHash = new(common.Hash) *query.ResultHash = query.calculateHash() - logRatio := math.Log(float64(len(query.results))*float64(s.finalizedBlock)/float64(query.ToBlock+1-query.FromBlock)) / math.Log(float64(s.finalizedBlock)*maxFilterResultSize) + maxFilterRange := min(s.maxFilterRange, s.blockLimit) + logRatio := math.Log(float64(len(query.results))*float64(maxFilterRange)/float64(query.ToBlock+1-query.FromBlock)) / math.Log(float64(maxFilterRange)*maxFilterResultSize) bucket := int(math.Floor(logRatio * filterBuckets)) if bucket >= filterBuckets { bucket = filterBuckets - 1 @@ -160,13 +164,13 @@ func (s *filterTestGen) storeQuery(query *filterQuery) { func (s *filterTestGen) extendRange(q *filterQuery) *filterQuery { rangeLen := q.ToBlock + 1 - q.FromBlock extLen := rand.Int63n(rangeLen) + 1 - if rangeLen+extLen > s.finalizedBlock { + if rangeLen+extLen > min(s.maxFilterRange, s.blockLimit) { return nil } extBefore := min(rand.Int63n(extLen+1), q.FromBlock) extAfter := extLen - extBefore - if q.ToBlock+extAfter > s.finalizedBlock { - d := q.ToBlock + extAfter - s.finalizedBlock + if q.ToBlock+extAfter > s.blockLimit { + d := q.ToBlock + extAfter - s.blockLimit extAfter -= d if extBefore+d <= q.FromBlock { extBefore += d @@ -203,7 +207,7 @@ func (s *filterTestGen) newQuery() *filterQuery { // newSeedQuery creates a query that gets all logs in a random non-finalized block. func (s *filterTestGen) newSeedQuery() *filterQuery { - block := rand.Int63n(s.finalizedBlock + 1) + block := rand.Int63n(s.blockLimit + 1) return &filterQuery{ FromBlock: block, ToBlock: block, @@ -358,6 +362,7 @@ func (s *filterTestGen) writeQueries() { func mustGetFinalizedBlock(client *client) int64 { ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) defer cancel() + header, err := client.Eth.HeaderByNumber(ctx, big.NewInt(int64(rpc.FinalizedBlockNumber))) if err != nil { exit(fmt.Errorf("could not fetch finalized header (error: %v)", err)) diff --git a/cmd/workload/main.go b/cmd/workload/main.go index 32618d6a79..8ac0e5b6cb 100644 --- a/cmd/workload/main.go +++ b/cmd/workload/main.go @@ -49,6 +49,7 @@ func init() { filterGenerateCommand, traceGenerateCommand, filterPerfCommand, + filterFuzzCommand, } } From 6337577434abcd99a24ff5e14dce9bd6381efbeb Mon Sep 17 00:00:00 2001 From: Delweng Date: Tue, 14 Oct 2025 01:58:50 +0800 Subject: [PATCH 51/61] p2p/discover: wait for bootstrap to be done (#32881) This ensures the node is ready to accept other nodes into the table before it is used in a test. Closes #32863 --- p2p/discover/v4_udp_test.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/p2p/discover/v4_udp_test.go b/p2p/discover/v4_udp_test.go index 44863183fa..287f0c34fa 100644 --- a/p2p/discover/v4_udp_test.go +++ b/p2p/discover/v4_udp_test.go @@ -575,6 +575,13 @@ func startLocalhostV4(t *testing.T, cfg Config) *UDPv4 { if err != nil { t.Fatal(err) } + + // Wait for bootstrap to complete. + select { + case <-udp.tab.initDone: + case <-time.After(5 * time.Second): + t.Fatalf("timed out waiting for table initialization") + } return udp } From 52c484de868dd6842b9205eede1d6add781bd424 Mon Sep 17 00:00:00 2001 From: Guillaume Ballet <3272758+gballet@users.noreply.github.com> Date: Tue, 14 Oct 2025 03:23:05 +0200 Subject: [PATCH 52/61] triedb/pathdb: catch int conversion overflow in 32-bit (#32899) The limit check for `MaxUint32` is done after the cast to `int`. On 64 bits machines, that will work without a problem. On 32 bits machines, that will always fail. The compiler catches it and refuses to build. Note that this only fixes the compiler build. ~~If the limit is above `MaxInt32` but strictly below `MaxUint32` then this will fail at runtime and we have another issue.~~ I checked and this should not happen during regular execution, although it might happen in tests. --- triedb/pathdb/history_trienode.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/triedb/pathdb/history_trienode.go b/triedb/pathdb/history_trienode.go index 2a4459d4ad..f5eb590a9a 100644 --- a/triedb/pathdb/history_trienode.go +++ b/triedb/pathdb/history_trienode.go @@ -161,7 +161,7 @@ func newTrienodeHistory(root common.Hash, parent common.Hash, block uint64, node // sharedLen returns the length of the common prefix shared by a and b. func sharedLen(a, b []byte) int { n := min(len(a), len(b)) - for i := 0; i < n; i++ { + for i := range n { if a[i] != b[i] { return i } @@ -295,7 +295,7 @@ func decodeHeader(data []byte) (*trienodeMetadata, []common.Hash, []uint32, []ui keyOffsets = make([]uint32, 0, count) valOffsets = make([]uint32, 0, count) ) - for i := 0; i < count; i++ { + for i := range count { n := trienodeMetadataSize + trienodeTrieHeaderSize*i owner := common.BytesToHash(data[n : n+common.HashLength]) if i != 0 && bytes.Compare(owner.Bytes(), owners[i-1].Bytes()) <= 0 { @@ -348,7 +348,7 @@ func decodeSingle(keySection []byte, onValue func([]byte, int, int) error) ([]st if len(keySection) < int(8*nRestarts)+4 { return nil, fmt.Errorf("key section too short, restarts: %d, size: %d", nRestarts, len(keySection)) } - for i := 0; i < int(nRestarts); i++ { + for i := range int(nRestarts) { o := len(keySection) - 4 - (int(nRestarts)-i)*8 keyOffset := binary.BigEndian.Uint32(keySection[o : o+4]) if i != 0 && keyOffset <= keyOffsets[i-1] { @@ -469,7 +469,7 @@ func (h *trienodeHistory) decode(header []byte, keySection []byte, valueSection h.nodeList = make(map[common.Hash][]string) h.nodes = make(map[common.Hash]map[string][]byte) - for i := 0; i < len(owners); i++ { + for i := range len(owners) { // Resolve the boundary of key section keyStart := keyOffsets[i] keyLimit := len(keySection) @@ -524,7 +524,7 @@ func newSingleTrienodeHistoryReader(id uint64, reader ethdb.AncientReader, keyRa } keyStart := int(keyRange.start) keyLimit := int(keyRange.limit) - if keyLimit == math.MaxUint32 { + if keyRange.limit == math.MaxUint32 { keyLimit = len(keyData) } if len(keyData) < keyStart || len(keyData) < keyLimit { From 00f6f2b32fd49255dd7fcab24f0326c1da91d1e5 Mon Sep 17 00:00:00 2001 From: Csaba Kiraly Date: Tue, 14 Oct 2025 05:01:43 +0200 Subject: [PATCH 53/61] eth/catalyst: remove useless log on enabling Engine API (#32901) --- eth/catalyst/api.go | 1 - 1 file changed, 1 deletion(-) diff --git a/eth/catalyst/api.go b/eth/catalyst/api.go index 6dfe24f729..75b263bf6b 100644 --- a/eth/catalyst/api.go +++ b/eth/catalyst/api.go @@ -47,7 +47,6 @@ import ( // Register adds the engine API to the full node. func Register(stack *node.Node, backend *eth.Ethereum) error { - log.Warn("Engine API enabled", "protocol", "eth") stack.RegisterAPIs([]rpc.API{ { Namespace: "engine", From fb8d2298b615aedd964635c6fb45587246da67ed Mon Sep 17 00:00:00 2001 From: Csaba Kiraly Date: Tue, 14 Oct 2025 05:03:31 +0200 Subject: [PATCH 54/61] eth: do not warn on switching from snap sync to full sync (#32900) This happens normally after a restart, so it is better to use Info level here. Signed-off-by: Csaba Kiraly --- eth/handler.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/eth/handler.go b/eth/handler.go index 304560a158..ff970e2ba6 100644 --- a/eth/handler.go +++ b/eth/handler.go @@ -181,8 +181,7 @@ func newHandler(config *handlerConfig) (*handler, error) { } else { head := h.chain.CurrentBlock() if head.Number.Uint64() > 0 && h.chain.HasState(head.Root) { - // Print warning log if database is not empty to run snap sync. - log.Warn("Switch sync mode from snap sync to full sync", "reason", "snap sync complete") + log.Info("Switch sync mode from snap sync to full sync", "reason", "snap sync complete") } else { // If snap sync was requested and our database is empty, grant it h.snapSync.Store(true) From e03d97a42052097d817eb13c22a2f1a6459518e1 Mon Sep 17 00:00:00 2001 From: rjl493456442 Date: Tue, 14 Oct 2025 14:40:04 +0800 Subject: [PATCH 55/61] core/txpool/legacypool: fix pricedList updates (#32906) This pr addresses a few issues brought by the #32270 - Add updates to pricedList after dropping transactions. - Remove redundant deletions in queue.evictList, since pool.removeTx(hash, true, true) already performs the removal. - Prevent duplicate addresses during promotion when Reset is not nil. --- core/txpool/legacypool/legacypool.go | 21 +++++------ core/txpool/legacypool/queue.go | 56 +++++++++++++++------------- 2 files changed, 40 insertions(+), 37 deletions(-) diff --git a/core/txpool/legacypool/legacypool.go b/core/txpool/legacypool/legacypool.go index b36d86dd19..ceedc74a53 100644 --- a/core/txpool/legacypool/legacypool.go +++ b/core/txpool/legacypool/legacypool.go @@ -367,8 +367,7 @@ func (pool *LegacyPool) loop() { // Handle inactive account transaction eviction case <-evict.C: pool.mu.Lock() - evicted := pool.queue.evict(false) - for _, hash := range evicted { + for _, hash := range pool.queue.evictList() { pool.removeTx(hash, true, true) } pool.mu.Unlock() @@ -813,7 +812,7 @@ func (pool *LegacyPool) isGapped(from common.Address, tx *types.Transaction) boo // // Note, this method assumes the pool lock is held! func (pool *LegacyPool) enqueueTx(hash common.Hash, tx *types.Transaction, addAll bool) (bool, error) { - replaced, err := pool.queue.add(hash, tx) + replaced, err := pool.queue.add(tx) if err != nil { return false, err } @@ -1093,7 +1092,7 @@ func (pool *LegacyPool) removeTx(hash common.Hash, outofbound bool, unreserve bo } } // Transaction is in the future queue - pool.queue.removeTx(addr, tx) + pool.queue.remove(addr, tx) return 0 } @@ -1241,7 +1240,7 @@ func (pool *LegacyPool) runReorg(done chan struct{}, reset *txpoolResetRequest, } } // Reset needs promote for all addresses - promoteAddrs = append(promoteAddrs, pool.queue.addresses()...) + promoteAddrs = pool.queue.addresses() } // Check for pending transactions for every account that sent new ones promoted := pool.promoteExecutables(promoteAddrs) @@ -1397,9 +1396,9 @@ func (pool *LegacyPool) reset(oldHead, newHead *types.Header) { func (pool *LegacyPool) promoteExecutables(accounts []common.Address) []*types.Transaction { gasLimit := pool.currentHead.Load().GasLimit promotable, dropped, removedAddresses := pool.queue.promoteExecutables(accounts, gasLimit, pool.currentState, pool.pendingNonces) - promoted := make([]*types.Transaction, 0, len(promotable)) - // promote all promoteable transactions + // promote all promotable transactions + promoted := make([]*types.Transaction, 0, len(promotable)) for _, tx := range promotable { from, _ := pool.signer.Sender(tx) if pool.promoteTx(from, tx.Hash(), tx) { @@ -1411,16 +1410,15 @@ func (pool *LegacyPool) promoteExecutables(accounts []common.Address) []*types.T for _, hash := range dropped { pool.all.Remove(hash) } + pool.priced.Removed(len(dropped)) // release all accounts that have no more transactions in the pool for _, addr := range removedAddresses { _, hasPending := pool.pending[addr] - _, hasQueued := pool.queue.get(addr) - if !hasPending && !hasQueued { + if !hasPending { pool.reserver.Release(addr) } } - return promoted } @@ -1510,10 +1508,11 @@ func (pool *LegacyPool) truncatePending() { func (pool *LegacyPool) truncateQueue() { removed, removedAddresses := pool.queue.truncate() - // remove all removable transactions + // Remove all removable transactions from the lookup and global price list for _, hash := range removed { pool.all.Remove(hash) } + pool.priced.Removed(len(removed)) for _, addr := range removedAddresses { _, hasPending := pool.pending[addr] diff --git a/core/txpool/legacypool/queue.go b/core/txpool/legacypool/queue.go index b8417064f7..a889debe37 100644 --- a/core/txpool/legacypool/queue.go +++ b/core/txpool/legacypool/queue.go @@ -27,6 +27,8 @@ import ( "github.com/ethereum/go-ethereum/log" ) +// queue manages nonce-gapped transactions that have been validated but are +// not yet processable. type queue struct { config Config signer types.Signer @@ -43,19 +45,17 @@ func newQueue(config Config, signer types.Signer) *queue { } } -func (q *queue) evict(force bool) []common.Hash { - removed := make([]common.Hash, 0) +// evictList returns the hashes of transactions that are old enough to be evicted. +func (q *queue) evictList() []common.Hash { + var removed []common.Hash for addr, list := range q.queued { - // Any transactions old enough should be removed - if force || time.Since(q.beats[addr]) > q.config.Lifetime { - list := list.Flatten() - for _, tx := range list { - q.removeTx(addr, tx) + if time.Since(q.beats[addr]) > q.config.Lifetime { + for _, tx := range list.Flatten() { removed = append(removed, tx.Hash()) } - queuedEvictionMeter.Mark(int64(len(list))) } } + queuedEvictionMeter.Mark(int64(len(removed))) return removed } @@ -100,7 +100,7 @@ func (q *queue) addresses() []common.Address { return addrs } -func (q queue) removeTx(addr common.Address, tx *types.Transaction) { +func (q *queue) remove(addr common.Address, tx *types.Transaction) { if future := q.queued[addr]; future != nil { if txOld := future.txs.Get(tx.Nonce()); txOld != nil && txOld.Hash() != tx.Hash() { // Edge case, a different transaction @@ -118,7 +118,7 @@ func (q queue) removeTx(addr common.Address, tx *types.Transaction) { } } -func (q *queue) add(hash common.Hash, tx *types.Transaction) (*common.Hash, error) { +func (q *queue) add(tx *types.Transaction) (*common.Hash, error) { // Try to insert the transaction into the future queue from, _ := types.Sender(q.signer, tx) // already validated if q.queued[from] == nil { @@ -149,15 +149,17 @@ func (q *queue) add(hash common.Hash, tx *types.Transaction) (*common.Hash, erro // for promotion any that are now executable. It also drops any transactions that are // deemed too old (nonce too low) or too costly (insufficient funds or over gas limit). // -// Returns three lists: all transactions that were removed from the queue and selected -// for promotion; all other transactions that were removed from the queue and dropped; -// the list of addresses removed. +// Returns three lists: +// - all transactions that were removed from the queue and selected for promotion; +// - all other transactions that were removed from the queue and dropped; +// - the list of addresses removed. func (q *queue) promoteExecutables(accounts []common.Address, gasLimit uint64, currentState *state.StateDB, nonces *noncer) ([]*types.Transaction, []common.Hash, []common.Address) { // Track the promotable transactions to broadcast them at once - var promotable []*types.Transaction - var dropped []common.Hash - var removedAddresses []common.Address - + var ( + promotable []*types.Transaction + dropped []common.Hash + removedAddresses []common.Address + ) // Iterate over all accounts and promote any executable transactions for _, addr := range accounts { list := q.queued[addr] @@ -170,6 +172,7 @@ func (q *queue) promoteExecutables(accounts []common.Address, gasLimit uint64, c dropped = append(dropped, tx.Hash()) } log.Trace("Removing old queued transactions", "count", len(forwards)) + // Drop all transactions that are too costly (low balance or out of gas) drops, _ := list.Filter(currentState.GetBalance(addr), gasLimit) for _, tx := range drops { @@ -205,9 +208,9 @@ func (q *queue) promoteExecutables(accounts []common.Address, gasLimit uint64, c } // truncate drops the oldest transactions from the queue until the total -// number is below the configured limit. -// Returns the hashes of all dropped transactions, and the addresses of -// accounts that became empty due to the truncation. +// number is below the configured limit. Returns the hashes of all dropped +// transactions and the addresses of accounts that became empty due to +// the truncation. func (q *queue) truncate() ([]common.Hash, []common.Address) { queued := uint64(0) for _, list := range q.queued { @@ -223,10 +226,12 @@ func (q *queue) truncate() ([]common.Hash, []common.Address) { addresses = append(addresses, addressByHeartbeat{addr, q.beats[addr]}) } sort.Sort(sort.Reverse(addresses)) - removed := make([]common.Hash, 0) - removedAddresses := make([]common.Address, 0) // Drop transactions until the total is below the limit + var ( + removed = make([]common.Hash, 0) + removedAddresses = make([]common.Address, 0) + ) for drop := queued - q.config.GlobalQueue; drop > 0 && len(addresses) > 0; { addr := addresses[len(addresses)-1] list := q.queued[addr.address] @@ -236,7 +241,7 @@ func (q *queue) truncate() ([]common.Hash, []common.Address) { // Drop all transactions if they are less than the overflow if size := uint64(list.Len()); size <= drop { for _, tx := range list.Flatten() { - q.removeTx(addr.address, tx) + q.remove(addr.address, tx) removed = append(removed, tx.Hash()) } drop -= size @@ -247,14 +252,13 @@ func (q *queue) truncate() ([]common.Hash, []common.Address) { // Otherwise drop only last few transactions txs := list.Flatten() for i := len(txs) - 1; i >= 0 && drop > 0; i-- { - q.removeTx(addr.address, txs[i]) + q.remove(addr.address, txs[i]) removed = append(removed, txs[i].Hash()) drop-- queuedRateLimitMeter.Mark(1) } } - - // no need to clear empty accounts, removeTx already does that + // No need to clear empty accounts, remove already does that return removed, removedAddresses } From 55a53208b7f6fe656fe13e0f34a652aaedad6e9e Mon Sep 17 00:00:00 2001 From: cui Date: Tue, 14 Oct 2025 17:07:48 +0800 Subject: [PATCH 56/61] accounts/abi: check presence of payable fallback or receive before proceeding with transfer (#32374) remove todo --- accounts/abi/abigen/bind_test.go | 6 +++--- accounts/abi/bind/v2/base.go | 6 ++++-- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/accounts/abi/abigen/bind_test.go b/accounts/abi/abigen/bind_test.go index b3c52e81e5..1651e637c8 100644 --- a/accounts/abi/abigen/bind_test.go +++ b/accounts/abi/abigen/bind_test.go @@ -485,13 +485,13 @@ var bindTests = []struct { contract Defaulter { address public caller; - function() { + fallback() external payable { caller = msg.sender; } } `, - []string{`6060604052606a8060106000396000f360606040523615601d5760e060020a6000350463fc9c8d3981146040575b605e6000805473ffffffffffffffffffffffffffffffffffffffff191633179055565b606060005473ffffffffffffffffffffffffffffffffffffffff1681565b005b6060908152602090f3`}, - []string{`[{"constant":true,"inputs":[],"name":"caller","outputs":[{"name":"","type":"address"}],"type":"function"}]`}, + []string{`608060405234801561000f575f80fd5b5061013d8061001d5f395ff3fe608060405260043610610021575f3560e01c8063fc9c8d391461006257610022565b5b335f806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055005b34801561006d575f80fd5b5061007661008c565b60405161008391906100ee565b60405180910390f35b5f8054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b5f73ffffffffffffffffffffffffffffffffffffffff82169050919050565b5f6100d8826100af565b9050919050565b6100e8816100ce565b82525050565b5f6020820190506101015f8301846100df565b9291505056fea26469706673582212201e9273ecfb1f534644c77f09a25c21baaba81cf1c444ebc071e12a225a23c72964736f6c63430008140033`}, + []string{`[{"stateMutability":"payable","type":"fallback"},{"inputs":[],"name":"caller","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"}]`}, ` "math/big" diff --git a/accounts/abi/bind/v2/base.go b/accounts/abi/bind/v2/base.go index f714848efb..4f2013b4a3 100644 --- a/accounts/abi/bind/v2/base.go +++ b/accounts/abi/bind/v2/base.go @@ -277,8 +277,10 @@ func (c *BoundContract) RawCreationTransact(opts *TransactOpts, calldata []byte) // Transfer initiates a plain transaction to move funds to the contract, calling // its default method if one is available. func (c *BoundContract) Transfer(opts *TransactOpts) (*types.Transaction, error) { - // todo(rjl493456442) check the payable fallback or receive is defined - // or not, reject invalid transaction at the first place + // Check if payable fallback or receive is defined + if !c.abi.HasReceive() && !(c.abi.HasFallback() && c.abi.Fallback.IsPayable()) { + return nil, fmt.Errorf("contract does not have a payable fallback or receive function") + } return c.transact(opts, &c.address, nil) } From f6064f32c4abeffa99c4c29674fe9ba756e90e08 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Tue, 14 Oct 2025 14:40:54 +0200 Subject: [PATCH 57/61] internal/ethapi: convert legacy blobtx proofs in sendRawTransaction (#32849) This adds a temporary conversion path for blob transactions with legacy proof sidecar. This feature will activate after Fusaka. We will phase this out when the fork has sufficiently settled and client side libraries have been upgraded to send the new proofs. --- internal/ethapi/api.go | 30 ++++++++++++++++++++++-------- 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index a2cb28d3b2..c10a4754af 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -1619,16 +1619,9 @@ func (api *TransactionAPI) SendTransaction(ctx context.Context, args Transaction // processing (signing + broadcast). func (api *TransactionAPI) FillTransaction(ctx context.Context, args TransactionArgs) (*SignTransactionResult, error) { // Set some sanity defaults and terminate on failure - sidecarVersion := types.BlobSidecarVersion0 - if len(args.Blobs) > 0 { - h := api.b.CurrentHeader() - if api.b.ChainConfig().IsOsaka(h.Number, h.Time) { - sidecarVersion = types.BlobSidecarVersion1 - } - } config := sidecarConfig{ blobSidecarAllowed: true, - blobSidecarVersion: sidecarVersion, + blobSidecarVersion: api.currentBlobSidecarVersion(), } if err := args.setDefaults(ctx, api.b, config); err != nil { return nil, err @@ -1642,6 +1635,14 @@ func (api *TransactionAPI) FillTransaction(ctx context.Context, args Transaction return &SignTransactionResult{data, tx}, nil } +func (api *TransactionAPI) currentBlobSidecarVersion() byte { + h := api.b.CurrentHeader() + if api.b.ChainConfig().IsOsaka(h.Number, h.Time) { + return types.BlobSidecarVersion1 + } + return types.BlobSidecarVersion0 +} + // SendRawTransaction will add the signed transaction to the transaction pool. // The sender is responsible for signing the transaction and using the correct nonce. func (api *TransactionAPI) SendRawTransaction(ctx context.Context, input hexutil.Bytes) (common.Hash, error) { @@ -1649,6 +1650,19 @@ func (api *TransactionAPI) SendRawTransaction(ctx context.Context, input hexutil if err := tx.UnmarshalBinary(input); err != nil { return common.Hash{}, err } + + // Convert legacy blob transaction proofs. + // TODO: remove in go-ethereum v1.17.x + if sc := tx.BlobTxSidecar(); sc != nil { + exp := api.currentBlobSidecarVersion() + if sc.Version == types.BlobSidecarVersion0 && exp == types.BlobSidecarVersion1 { + if err := sc.ToV1(); err != nil { + return common.Hash{}, fmt.Errorf("blob sidecar conversion failed: %v", err) + } + tx = tx.WithBlobTxSidecar(sc) + } + } + return SubmitTransaction(ctx, api.b, tx) } From 3cfc33477bcd84aefa8cfbc8cee33625b8cfea6d Mon Sep 17 00:00:00 2001 From: mishraa-G Date: Wed, 15 Oct 2025 13:09:00 +0530 Subject: [PATCH 58/61] rpc: fix flaky test TestServerWebsocketReadLimit (#32889) --- rpc/server_test.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/rpc/server_test.go b/rpc/server_test.go index a38a64b080..8334d4e80d 100644 --- a/rpc/server_test.go +++ b/rpc/server_test.go @@ -273,7 +273,8 @@ func TestServerWebsocketReadLimit(t *testing.T) { } } else if !errors.Is(err, websocket.ErrReadLimit) && !strings.Contains(strings.ToLower(err.Error()), "1009") && - !strings.Contains(strings.ToLower(err.Error()), "message too big") { + !strings.Contains(strings.ToLower(err.Error()), "message too big") && + !strings.Contains(strings.ToLower(err.Error()), "connection reset by peer") { // Not the error we expect from exceeding the message size limit. t.Fatalf("unexpected error for read limit violation: %v", err) } From 40505a9bc065033472be5c0bcaf2882efd421ce2 Mon Sep 17 00:00:00 2001 From: Delweng Date: Wed, 15 Oct 2025 16:24:48 +0800 Subject: [PATCH 59/61] eth/protocols/eth: reject message containing duplicated txs and drop peer (#32728) Drop peer if sending the same transaction multiple times in a single message. Fixes https://github.com/ethereum/go-ethereum/issues/32724 --------- Signed-off-by: Csaba Kiraly Co-authored-by: Gary Rong Co-authored-by: Csaba Kiraly --- eth/protocols/eth/handlers.go | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/eth/protocols/eth/handlers.go b/eth/protocols/eth/handlers.go index 15ad048bcf..aad3353d88 100644 --- a/eth/protocols/eth/handlers.go +++ b/eth/protocols/eth/handlers.go @@ -494,12 +494,19 @@ func handleTransactions(backend Backend, msg Decoder, peer *Peer) error { if err := msg.Decode(&txs); err != nil { return err } + // Duplicate transactions are not allowed + seen := make(map[common.Hash]struct{}) for i, tx := range txs { // Validate and mark the remote transaction if tx == nil { return fmt.Errorf("Transactions: transaction %d is nil", i) } - peer.markTransaction(tx.Hash()) + hash := tx.Hash() + if _, exists := seen[hash]; exists { + return fmt.Errorf("Transactions: multiple copies of the same hash %v", hash) + } + seen[hash] = struct{}{} + peer.markTransaction(hash) } return backend.Handle(peer, &txs) } @@ -514,12 +521,19 @@ func handlePooledTransactions(backend Backend, msg Decoder, peer *Peer) error { if err := msg.Decode(&txs); err != nil { return err } + // Duplicate transactions are not allowed + seen := make(map[common.Hash]struct{}) for i, tx := range txs.PooledTransactionsResponse { // Validate and mark the remote transaction if tx == nil { return fmt.Errorf("PooledTransactions: transaction %d is nil", i) } - peer.markTransaction(tx.Hash()) + hash := tx.Hash() + if _, exists := seen[hash]; exists { + return fmt.Errorf("PooledTransactions: multiple copies of the same hash %v", hash) + } + seen[hash] = struct{}{} + peer.markTransaction(hash) } requestTracker.Fulfil(peer.id, peer.version, PooledTransactionsMsg, txs.RequestId) From 7c107c2691fa66a1da60e2b95f5946c3a3921b00 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Wed, 15 Oct 2025 11:51:33 +0200 Subject: [PATCH 60/61] p2p/discover: remove hot-spin in table refresh trigger (#32912) This fixes a regression introduced in #32518. In that PR, we removed the slowdown logic that would throttle lookups when the table runs empty. Said logic was originally added in #20389. Usually it's fine, but there exist pathological cases, such as hive tests, where the node can only discover one other node, so it can only ever query that node and won't get any results. In cases like these, we need to throttle the creation of lookups to avoid crazy CPU usage. --- p2p/discover/lookup.go | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/p2p/discover/lookup.go b/p2p/discover/lookup.go index 9cca0118ac..416256fb36 100644 --- a/p2p/discover/lookup.go +++ b/p2p/discover/lookup.go @@ -153,6 +153,7 @@ type lookupIterator struct { cancel func() lookup *lookup tabRefreshing <-chan struct{} + lastLookup time.Time } type lookupFunc func(ctx context.Context) *lookup @@ -185,6 +186,9 @@ func (it *lookupIterator) Next() bool { return false } if it.lookup == nil { + // Ensure enough time has passed between lookup creations. + it.slowdown() + it.lookup = it.nextLookup(it.ctx) if it.lookup.empty() { // If the lookup is empty right after creation, it means the local table @@ -235,6 +239,25 @@ func (it *lookupIterator) lookupFailed(tab *Table, timeout time.Duration) { tab.waitForNodes(tout, 1) } +// slowdown applies a delay between creating lookups. This exists to prevent hot-spinning +// in some test environments where lookups don't yield any results. +func (it *lookupIterator) slowdown() { + const minInterval = 1 * time.Second + + now := time.Now() + diff := now.Sub(it.lastLookup) + it.lastLookup = now + if diff > minInterval { + return + } + wait := time.NewTimer(diff) + defer wait.Stop() + select { + case <-wait.C: + case <-it.ctx.Done(): + } +} + // Close ends the iterator. func (it *lookupIterator) Close() { it.cancel() From 32ccb548d34b26edb665446d1695870573d136c7 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Thu, 16 Oct 2025 09:58:47 +0200 Subject: [PATCH 61/61] version: release go-ethereum v1.16.5 stable --- version/version.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/version/version.go b/version/version.go index db4e5394b9..f50a9892a4 100644 --- a/version/version.go +++ b/version/version.go @@ -17,8 +17,8 @@ package version const ( - Major = 1 // Major version component of the current release - Minor = 16 // Minor version component of the current release - Patch = 5 // Patch version component of the current release - Meta = "unstable" // Version metadata to append to the version string + Major = 1 // Major version component of the current release + Minor = 16 // Minor version component of the current release + Patch = 5 // Patch version component of the current release + Meta = "stable" // Version metadata to append to the version string )