From 8e3cd41b0490dc54022c0384c30c576b10c7f8e9 Mon Sep 17 00:00:00 2001 From: maskpp Date: Thu, 20 Mar 2025 13:14:13 +0800 Subject: [PATCH 1/7] cmd/utils: force hash scheme for archive mode (#31439) --- cmd/utils/flags.go | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index 77dac8bd1a..ae58c2d053 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -1659,12 +1659,16 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *ethconfig.Config) { log.Warn("The flag --txlookuplimit is deprecated and will be removed, please use --history.transactions") cfg.TransactionHistory = ctx.Uint64(TxLookupLimitFlag.Name) } - if ctx.String(GCModeFlag.Name) == "archive" && cfg.TransactionHistory != 0 { - cfg.TransactionHistory = 0 - log.Warn("Disabled transaction unindexing for archive node") + if ctx.String(GCModeFlag.Name) == "archive" { + if cfg.TransactionHistory != 0 { + cfg.TransactionHistory = 0 + log.Warn("Disabled transaction unindexing for archive node") + } - cfg.StateScheme = rawdb.HashScheme - log.Warn("Forcing hash state-scheme for archive mode") + if cfg.StateScheme != rawdb.HashScheme { + cfg.StateScheme = rawdb.HashScheme + log.Warn("Forcing hash state-scheme for archive mode") + } } if ctx.IsSet(LogHistoryFlag.Name) { cfg.LogHistory = ctx.Uint64(LogHistoryFlag.Name) From 03cc2942c2c25208dac1b7c8fc1bc19f3d72fd97 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felf=C3=B6ldi=20Zsolt?= Date: Thu, 20 Mar 2025 09:23:10 +0100 Subject: [PATCH 2/7] cmd/workload: fixed filter test request error handling (#31424) This PR fixes the broken request error handling of the workload filter tests. Until now `validateHistoryPruneErr` was invoked with `fq.Err` as an input which was always nil and a timeout or http error was reported as a result content mismatch. Also, in case of `errPrunedHistory` it is wrong to return here without setting an error because then it will look like a valid empty result and the check will later fail. So instead `errPrunedHistory` is always returned now (without printing an error message) and the callers of `run` should handle this special case (typically ignore silently). --- cmd/workload/filtertest.go | 21 ++++++++++-------- cmd/workload/filtertestgen.go | 24 ++++----------------- cmd/workload/filtertestperf.go | 39 ++++++++++++++++++++++++++++------ 3 files changed, 49 insertions(+), 35 deletions(-) diff --git a/cmd/workload/filtertest.go b/cmd/workload/filtertest.go index bc76857911..52dd6e41ad 100644 --- a/cmd/workload/filtertest.go +++ b/cmd/workload/filtertest.go @@ -109,6 +109,9 @@ func (s *filterTestSuite) filterFullRange(t *utesting.T) { func (s *filterTestSuite) queryAndCheck(t *utesting.T, query *filterQuery) { query.run(s.cfg.client, s.cfg.historyPruneBlock) + if query.Err == errPrunedHistory { + return + } if query.Err != nil { t.Errorf("Filter query failed (fromBlock: %d toBlock: %d addresses: %v topics: %v error: %v)", query.FromBlock, query.ToBlock, query.Address, query.Topics, query.Err) return @@ -126,6 +129,9 @@ func (s *filterTestSuite) fullRangeQueryAndCheck(t *utesting.T, query *filterQue Topics: query.Topics, } frQuery.run(s.cfg.client, s.cfg.historyPruneBlock) + if frQuery.Err == errPrunedHistory { + return + } if frQuery.Err != nil { t.Errorf("Full range filter query failed (addresses: %v topics: %v error: %v)", frQuery.Address, frQuery.Topics, frQuery.Err) return @@ -206,14 +212,11 @@ func (fq *filterQuery) run(client *client, historyPruneBlock *uint64) { Addresses: fq.Address, Topics: fq.Topics, }) - if err != nil { - if err = validateHistoryPruneErr(fq.Err, uint64(fq.FromBlock), historyPruneBlock); err == errPrunedHistory { - return - } else if err != nil { - fmt.Printf("Filter query failed: fromBlock: %d toBlock: %d addresses: %v topics: %v error: %v\n", - fq.FromBlock, fq.ToBlock, fq.Address, fq.Topics, err) - } - fq.Err = err - } fq.results = logs + fq.Err = validateHistoryPruneErr(err, uint64(fq.FromBlock), historyPruneBlock) +} + +func (fq *filterQuery) printError() { + fmt.Printf("Filter query failed: fromBlock: %d toBlock: %d addresses: %v topics: %v error: %v\n", + fq.FromBlock, fq.ToBlock, fq.Address, fq.Topics, fq.Err) } diff --git a/cmd/workload/filtertestgen.go b/cmd/workload/filtertestgen.go index e50ec57b47..6d1f639819 100644 --- a/cmd/workload/filtertestgen.go +++ b/cmd/workload/filtertestgen.go @@ -40,7 +40,6 @@ var ( Action: filterGenCmd, Flags: []cli.Flag{ filterQueryFileFlag, - filterErrorFileFlag, }, } filterQueryFileFlag = &cli.StringFlag{ @@ -72,8 +71,8 @@ func filterGenCmd(ctx *cli.Context) error { query := f.newQuery() query.run(f.client, nil) if query.Err != nil { - f.errors = append(f.errors, query) - continue + query.printError() + exit("filter query failed") } if len(query.results) > 0 && len(query.results) <= maxFilterResultSize { for { @@ -90,8 +89,8 @@ func filterGenCmd(ctx *cli.Context) error { ) } if extQuery.Err != nil { - f.errors = append(f.errors, extQuery) - break + extQuery.printError() + exit("filter query failed") } if len(extQuery.results) > maxFilterResultSize { break @@ -101,7 +100,6 @@ func filterGenCmd(ctx *cli.Context) error { f.storeQuery(query) if time.Since(lastWrite) > time.Second*10 { f.writeQueries() - f.writeErrors() lastWrite = time.Now() } } @@ -112,18 +110,15 @@ func filterGenCmd(ctx *cli.Context) error { type filterTestGen struct { client *client queryFile string - errorFile string finalizedBlock int64 queries [filterBuckets][]*filterQuery - errors []*filterQuery } func newFilterTestGen(ctx *cli.Context) *filterTestGen { return &filterTestGen{ client: makeClient(ctx), queryFile: ctx.String(filterQueryFileFlag.Name), - errorFile: ctx.String(filterErrorFileFlag.Name), } } @@ -360,17 +355,6 @@ func (s *filterTestGen) writeQueries() { file.Close() } -// writeQueries serializes the generated errors to the error file. -func (s *filterTestGen) writeErrors() { - file, err := os.Create(s.errorFile) - if err != nil { - exit(fmt.Errorf("Error creating filter error file %s: %v", s.errorFile, err)) - return - } - defer file.Close() - json.NewEncoder(file).Encode(s.errors) -} - func mustGetFinalizedBlock(client *client) int64 { ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) defer cancel() diff --git a/cmd/workload/filtertestperf.go b/cmd/workload/filtertestperf.go index 7f4cad4ab9..c7d2fdd02a 100644 --- a/cmd/workload/filtertestperf.go +++ b/cmd/workload/filtertestperf.go @@ -17,8 +17,10 @@ package main import ( + "encoding/json" "fmt" "math/rand" + "os" "slices" "sort" "time" @@ -41,7 +43,7 @@ var ( } ) -const passCount = 1 +const passCount = 3 func filterPerfCmd(ctx *cli.Context) error { cfg := testConfigFromCLI(ctx) @@ -61,7 +63,10 @@ func filterPerfCmd(ctx *cli.Context) error { } // Run test queries. - var failed, mismatch int + var ( + failed, pruned, mismatch int + errors []*filterQuery + ) for i := 1; i <= passCount; i++ { fmt.Println("Performance test pass", i, "/", passCount) for len(queries) > 0 { @@ -71,27 +76,35 @@ func filterPerfCmd(ctx *cli.Context) error { queries = queries[:len(queries)-1] start := time.Now() qt.query.run(cfg.client, cfg.historyPruneBlock) + if qt.query.Err == errPrunedHistory { + pruned++ + continue + } qt.runtime = append(qt.runtime, time.Since(start)) slices.Sort(qt.runtime) qt.medianTime = qt.runtime[len(qt.runtime)/2] if qt.query.Err != nil { + qt.query.printError() + errors = append(errors, qt.query) failed++ continue } if rhash := qt.query.calculateHash(); *qt.query.ResultHash != rhash { fmt.Printf("Filter query result mismatch: fromBlock: %d toBlock: %d addresses: %v topics: %v expected hash: %064x calculated hash: %064x\n", qt.query.FromBlock, qt.query.ToBlock, qt.query.Address, qt.query.Topics, *qt.query.ResultHash, rhash) + errors = append(errors, qt.query) + mismatch++ continue } processed = append(processed, qt) if len(processed)%50 == 0 { - fmt.Println(" processed:", len(processed), "remaining", len(queries), "failed:", failed, "result mismatch:", mismatch) + fmt.Println(" processed:", len(processed), "remaining", len(queries), "failed:", failed, "pruned:", pruned, "result mismatch:", mismatch) } } queries, processed = processed, nil } // Show results and stats. - fmt.Println("Performance test finished; processed:", len(queries), "failed:", failed, "result mismatch:", mismatch) + fmt.Println("Performance test finished; processed:", len(queries), "failed:", failed, "pruned:", pruned, "result mismatch:", mismatch) stats := make([]bucketStats, len(f.queries)) var wildcardStats bucketStats for _, qt := range queries { @@ -114,11 +127,14 @@ func filterPerfCmd(ctx *cli.Context) error { sort.Slice(queries, func(i, j int) bool { return queries[i].medianTime > queries[j].medianTime }) - for i := 0; i < 10; i++ { - q := queries[i] + for i, q := range queries { + if i >= 10 { + break + } fmt.Printf("Most expensive query #%-2d median runtime: %13v max runtime: %13v result count: %4d fromBlock: %9d toBlock: %9d addresses: %v topics: %v\n", i+1, q.medianTime, q.runtime[len(q.runtime)-1], len(q.query.results), q.query.FromBlock, q.query.ToBlock, q.query.Address, q.query.Topics) } + writeErrors(ctx.String(filterErrorFileFlag.Name), errors) return nil } @@ -135,3 +151,14 @@ func (st *bucketStats) print(name string) { fmt.Printf("%-20s queries: %4d average block length: %12.2f average log count: %7.2f average runtime: %13v\n", name, st.count, float64(st.blocks)/float64(st.count), float64(st.logs)/float64(st.count), st.runtime/time.Duration(st.count)) } + +// writeQueries serializes the generated errors to the error file. +func writeErrors(errorFile string, errors []*filterQuery) { + file, err := os.Create(errorFile) + if err != nil { + exit(fmt.Errorf("Error creating filter error file %s: %v", errorFile, err)) + return + } + defer file.Close() + json.NewEncoder(file).Encode(errors) +} From 43883c64566602305c079ed6a7c83183f0443dfb Mon Sep 17 00:00:00 2001 From: Sina M <1591639+s1na@users.noreply.github.com> Date: Thu, 20 Mar 2025 10:20:51 +0100 Subject: [PATCH 3/7] eth/tracers: hex-encode returnValue (#31216) This is a **breaking change** to the opcode tracer. The top-level `returnValue` field of a trace will be now hex-encoded. If the return data is empty, this field will contain "0x". Fixes #31196 --- eth/tracers/api_test.go | 42 ++++++++++++++++++------------------ eth/tracers/logger/logger.go | 7 +++--- 2 files changed, 24 insertions(+), 25 deletions(-) diff --git a/eth/tracers/api_test.go b/eth/tracers/api_test.go index 796719bd63..c28abba85f 100644 --- a/eth/tracers/api_test.go +++ b/eth/tracers/api_test.go @@ -354,7 +354,7 @@ func TestTraceCall(t *testing.T) { }, config: nil, expectErr: nil, - expect: `{"gas":21000,"failed":false,"returnValue":"","structLogs":[]}`, + expect: `{"gas":21000,"failed":false,"returnValue":"0x","structLogs":[]}`, }, // Standard JSON trace upon the head, plain transfer. { @@ -366,7 +366,7 @@ func TestTraceCall(t *testing.T) { }, config: nil, expectErr: nil, - expect: `{"gas":21000,"failed":false,"returnValue":"","structLogs":[]}`, + expect: `{"gas":21000,"failed":false,"returnValue":"0x","structLogs":[]}`, }, // Upon the last state, default to the post block's state { @@ -377,7 +377,7 @@ func TestTraceCall(t *testing.T) { Value: (*hexutil.Big)(new(big.Int).Add(big.NewInt(params.Ether), big.NewInt(100))), }, config: nil, - expect: `{"gas":21000,"failed":false,"returnValue":"","structLogs":[]}`, + expect: `{"gas":21000,"failed":false,"returnValue":"0x","structLogs":[]}`, }, // Before the first transaction, should be failed { @@ -411,7 +411,7 @@ func TestTraceCall(t *testing.T) { }, config: &TraceCallConfig{TxIndex: uintPtr(2)}, expectErr: nil, - expect: `{"gas":21000,"failed":false,"returnValue":"","structLogs":[]}`, + expect: `{"gas":21000,"failed":false,"returnValue":"0x","structLogs":[]}`, }, // Standard JSON trace upon the non-existent block, error expects { @@ -435,7 +435,7 @@ func TestTraceCall(t *testing.T) { }, config: nil, expectErr: nil, - expect: `{"gas":21000,"failed":false,"returnValue":"","structLogs":[]}`, + expect: `{"gas":21000,"failed":false,"returnValue":"0x","structLogs":[]}`, }, // Tracing on 'pending' should fail: { @@ -458,7 +458,7 @@ func TestTraceCall(t *testing.T) { BlockOverrides: &override.BlockOverrides{Number: (*hexutil.Big)(big.NewInt(0x1337))}, }, expectErr: nil, - expect: ` {"gas":53018,"failed":false,"returnValue":"","structLogs":[ + expect: ` {"gas":53018,"failed":false,"returnValue":"0x","structLogs":[ {"pc":0,"op":"NUMBER","gas":24946984,"gasCost":2,"depth":1,"stack":[]}, {"pc":1,"op":"STOP","gas":24946982,"gasCost":0,"depth":1,"stack":["0x1337"]}]}`, }, @@ -535,7 +535,7 @@ func TestTraceTransaction(t *testing.T) { if !reflect.DeepEqual(have, &logger.ExecutionResult{ Gas: params.TxGas, Failed: false, - ReturnValue: "", + ReturnValue: []byte{}, StructLogs: []json.RawMessage{}, }) { t.Error("Transaction tracing result is different") @@ -596,7 +596,7 @@ func TestTraceBlock(t *testing.T) { // Trace head block { blockNumber: rpc.BlockNumber(genBlocks), - want: fmt.Sprintf(`[{"txHash":"%v","result":{"gas":21000,"failed":false,"returnValue":"","structLogs":[]}}]`, txHash), + want: fmt.Sprintf(`[{"txHash":"%v","result":{"gas":21000,"failed":false,"returnValue":"0x","structLogs":[]}}]`, txHash), }, // Trace non-existent block { @@ -606,12 +606,12 @@ func TestTraceBlock(t *testing.T) { // Trace latest block { blockNumber: rpc.LatestBlockNumber, - want: fmt.Sprintf(`[{"txHash":"%v","result":{"gas":21000,"failed":false,"returnValue":"","structLogs":[]}}]`, txHash), + want: fmt.Sprintf(`[{"txHash":"%v","result":{"gas":21000,"failed":false,"returnValue":"0x","structLogs":[]}}]`, txHash), }, // Trace pending block { blockNumber: rpc.PendingBlockNumber, - want: fmt.Sprintf(`[{"txHash":"%v","result":{"gas":21000,"failed":false,"returnValue":"","structLogs":[]}}]`, txHash), + want: fmt.Sprintf(`[{"txHash":"%v","result":{"gas":21000,"failed":false,"returnValue":"0x","structLogs":[]}}]`, txHash), }, } for i, tc := range testSuite { @@ -704,7 +704,7 @@ func TestTracingWithOverrides(t *testing.T) { randomAccounts[0].addr: override.OverrideAccount{Balance: newRPCBalance(new(big.Int).Mul(big.NewInt(1), big.NewInt(params.Ether)))}, }, }, - want: `{"gas":21000,"failed":false,"returnValue":""}`, + want: `{"gas":21000,"failed":false,"returnValue":"0x"}`, }, // Invalid call without state overriding { @@ -749,7 +749,7 @@ func TestTracingWithOverrides(t *testing.T) { }, }, }, - want: `{"gas":23347,"failed":false,"returnValue":"000000000000000000000000000000000000000000000000000000000000007b"}`, + want: `{"gas":23347,"failed":false,"returnValue":"0x000000000000000000000000000000000000000000000000000000000000007b"}`, }, { // Override blocknumber blockNumber: rpc.LatestBlockNumber, @@ -761,7 +761,7 @@ func TestTracingWithOverrides(t *testing.T) { config: &TraceCallConfig{ BlockOverrides: &override.BlockOverrides{Number: (*hexutil.Big)(big.NewInt(0x1337))}, }, - want: `{"gas":59537,"failed":false,"returnValue":"0000000000000000000000000000000000000000000000000000000000001337"}`, + want: `{"gas":59537,"failed":false,"returnValue":"0x0000000000000000000000000000000000000000000000000000000000001337"}`, }, { // Override blocknumber, and query a blockhash blockNumber: rpc.LatestBlockNumber, @@ -781,7 +781,7 @@ func TestTracingWithOverrides(t *testing.T) { config: &TraceCallConfig{ BlockOverrides: &override.BlockOverrides{Number: (*hexutil.Big)(big.NewInt(0x1337))}, }, - want: `{"gas":72666,"failed":false,"returnValue":"000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"}`, + want: `{"gas":72666,"failed":false,"returnValue":"0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"}`, }, /* pragma solidity =0.8.12; @@ -815,7 +815,7 @@ func TestTracingWithOverrides(t *testing.T) { }, }, }, - want: `{"gas":44100,"failed":false,"returnValue":"0000000000000000000000000000000000000000000000000000000000000001"}`, + want: `{"gas":44100,"failed":false,"returnValue":"0x0000000000000000000000000000000000000000000000000000000000000001"}`, }, { // Same again, this time with storage override blockNumber: rpc.LatestBlockNumber, @@ -833,7 +833,7 @@ func TestTracingWithOverrides(t *testing.T) { }, }, //want: `{"gas":46900,"failed":false,"returnValue":"0000000000000000000000000000000000000000000000000000000000000539"}`, - want: `{"gas":44100,"failed":false,"returnValue":"0000000000000000000000000000000000000000000000000000000000000001"}`, + want: `{"gas":44100,"failed":false,"returnValue":"0x0000000000000000000000000000000000000000000000000000000000000001"}`, }, { // No state override blockNumber: rpc.LatestBlockNumber, @@ -863,7 +863,7 @@ func TestTracingWithOverrides(t *testing.T) { }, }, }, - want: `{"gas":25288,"failed":false,"returnValue":"0000000000000000000000000000000000000000000000000000000000000077"}`, + want: `{"gas":25288,"failed":false,"returnValue":"0x0000000000000000000000000000000000000000000000000000000000000077"}`, }, { // Full state override // The original storage is @@ -901,7 +901,7 @@ func TestTracingWithOverrides(t *testing.T) { }, }, }, - want: `{"gas":25288,"failed":false,"returnValue":"0000000000000000000000000000000000000000000000000000000000000011"}`, + want: `{"gas":25288,"failed":false,"returnValue":"0x0000000000000000000000000000000000000000000000000000000000000011"}`, }, { // Partial state override // The original storage is @@ -939,7 +939,7 @@ func TestTracingWithOverrides(t *testing.T) { }, }, }, - want: `{"gas":25288,"failed":false,"returnValue":"0000000000000000000000000000000000000000000000000000000000000055"}`, + want: `{"gas":25288,"failed":false,"returnValue":"0x0000000000000000000000000000000000000000000000000000000000000055"}`, }, { // Call to precompile ECREC (0x01), but code was modified to add 1 to input blockNumber: rpc.LatestBlockNumber, @@ -1084,7 +1084,7 @@ func TestTraceChain(t *testing.T) { backend.relHook = func() { rel.Add(1) } api := NewAPI(backend) - single := `{"txHash":"0x0000000000000000000000000000000000000000000000000000000000000000","result":{"gas":21000,"failed":false,"returnValue":"","structLogs":[]}}` + single := `{"txHash":"0x0000000000000000000000000000000000000000000000000000000000000000","result":{"gas":21000,"failed":false,"returnValue":"0x","structLogs":[]}}` var cases = []struct { start uint64 end uint64 @@ -1198,7 +1198,7 @@ func TestTraceBlockWithBasefee(t *testing.T) { // Trace head block { blockNumber: rpc.BlockNumber(genBlocks), - want: fmt.Sprintf(`[{"txHash":"%#x","result":{"gas":21002,"failed":false,"returnValue":"","structLogs":[{"pc":0,"op":"BASEFEE","gas":84000,"gasCost":2,"depth":1,"stack":[]},{"pc":1,"op":"STOP","gas":83998,"gasCost":0,"depth":1,"stack":["%#x"]}]}}]`, txHash, baseFee), + want: fmt.Sprintf(`[{"txHash":"%#x","result":{"gas":21002,"failed":false,"returnValue":"0x","structLogs":[{"pc":0,"op":"BASEFEE","gas":84000,"gasCost":2,"depth":1,"stack":[]},{"pc":1,"op":"STOP","gas":83998,"gasCost":0,"depth":1,"stack":["%#x"]}]}}]`, txHash, baseFee), }, } for i, tc := range testSuite { diff --git a/eth/tracers/logger/logger.go b/eth/tracers/logger/logger.go index 02ff8146fb..a28cecf138 100644 --- a/eth/tracers/logger/logger.go +++ b/eth/tracers/logger/logger.go @@ -350,14 +350,13 @@ func (l *StructLogger) GetResult() (json.RawMessage, error) { failed := l.err != nil returnData := common.CopyBytes(l.output) // Return data when successful and revert reason when reverted, otherwise empty. - returnVal := fmt.Sprintf("%x", returnData) if failed && !errors.Is(l.err, vm.ErrExecutionReverted) { - returnVal = "" + returnData = []byte{} } return json.Marshal(&ExecutionResult{ Gas: l.usedGas, Failed: failed, - ReturnValue: returnVal, + ReturnValue: returnData, StructLogs: l.logs, }) } @@ -527,6 +526,6 @@ func (t *mdLogger) OnFault(pc uint64, op byte, gas, cost uint64, scope tracing.O type ExecutionResult struct { Gas uint64 `json:"gas"` Failed bool `json:"failed"` - ReturnValue string `json:"returnValue"` + ReturnValue hexutil.Bytes `json:"returnValue"` StructLogs []json.RawMessage `json:"structLogs"` } From 0a8f41e2cb9b28210e73b9509bf645ec56eb1f5f Mon Sep 17 00:00:00 2001 From: rjl493456442 Date: Thu, 20 Mar 2025 20:33:13 +0800 Subject: [PATCH 4/7] eth/tracers: fix test (#31445) This pull request fixes a broken unit test ``` === CONT TestTracingWithOverrides api_test.go:1012: result: {"gas":21167,"failed":false,"returnValue":"0x0000000000000000000000000000000000000000000000000000000000000002","structLogs":[{"pc":0,"op":"PUSH1","gas":24978860,"gasCost":3,"depth":1,"stack":[]},{"pc":2,"op":"CALLDATALOAD","gas":24978857,"gasCost":3,"depth":1,"stack":["0x0"]},{"pc":3,"op":"PUSH1","gas":24978854,"gasCost":3,"depth":1,"stack":["0x1"]},{"pc":5,"op":"ADD","gas":24978851,"gasCost":3,"depth":1,"stack":["0x1","0x1"]},{"pc":6,"op":"PUSH1","gas":24978848,"gasCost":3,"depth":1,"stack":["0x2"]},{"pc":8,"op":"MSTORE","gas":24978845,"gasCost":6,"depth":1,"stack":["0x2","0x0"]},{"pc":9,"op":"PUSH1","gas":24978839,"gasCost":3,"depth":1,"stack":[]},{"pc":11,"op":"PUSH1","gas":24978836,"gasCost":3,"depth":1,"stack":["0x20"]},{"pc":13,"op":"RETURN","gas":24978833,"gasCost":0,"depth":1,"stack":["0x20","0x0"]}]} api_test.go:1013: test 10, result mismatch, have {21167 false 0x0000000000000000000000000000000000000000000000000000000000000002} , want {21167 false 0000000000000000000000000000000000000000000000000000000000000002} api_test.go:1012: result: {"gas":25664,"failed":false,"returnValue":"0x000000000000000000000000c6e93f4c1920eaeaa1e699f76a7a8c18e3056074","structLogs":[]} api_test.go:1013: test 11, result mismatch, have {25664 false 0x000000000000000000000000c6e93f4c1920eaeaa1e699f76a7a8c18e3056074} , want {25664 false 000000000000000000000000c6e93f4c1920eaeaa1e699f76a7a8c18e3056074} ``` --- eth/tracers/api_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/eth/tracers/api_test.go b/eth/tracers/api_test.go index c28abba85f..529448e397 100644 --- a/eth/tracers/api_test.go +++ b/eth/tracers/api_test.go @@ -960,7 +960,7 @@ func TestTracingWithOverrides(t *testing.T) { }, }, }, - want: `{"gas":21167,"failed":false,"returnValue":"0000000000000000000000000000000000000000000000000000000000000002"}`, + want: `{"gas":21167,"failed":false,"returnValue":"0x0000000000000000000000000000000000000000000000000000000000000002"}`, }, { // Call to ECREC Precompiled on a different address, expect the original behaviour of ECREC precompile blockNumber: rpc.LatestBlockNumber, @@ -981,7 +981,7 @@ func TestTracingWithOverrides(t *testing.T) { }, }, }, - want: `{"gas":25664,"failed":false,"returnValue":"000000000000000000000000c6e93f4c1920eaeaa1e699f76a7a8c18e3056074"}`, + want: `{"gas":25664,"failed":false,"returnValue":"0x000000000000000000000000c6e93f4c1920eaeaa1e699f76a7a8c18e3056074"}`, }, } for i, tc := range testSuite { From 9fc2bbe1ceaaa3889d0a8eee8c9dcfb6ddefb95c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felf=C3=B6ldi=20Zsolt?= Date: Thu, 20 Mar 2025 14:13:58 +0100 Subject: [PATCH 5/7] core/filtermaps: allow log search while head indexing (#31429) This PR changes the matcher syncing conditions so that it is possible to run a search while head indexing is in progress. Previously it was a requirement to have the head indexed in order to perform matcher sync before and after a search. This was unnecessarily strict as the purpose was just to avoid syncing the valid range with the temporary shortened indexed range applied while updating existing head maps. Now the sync condition explicitly checks whether the indexer has a temporary indexed range with some head maps being partially updated. It also fixes a deadlock that happened when matcher synchronization was attempted in the event handler called from the `writeFinishedMaps` periodical callback. --- core/filtermaps/filtermaps.go | 10 ++++++---- core/filtermaps/indexer.go | 14 +++++++++----- core/filtermaps/map_renderer.go | 8 ++++++-- core/filtermaps/matcher_backend.go | 2 +- 4 files changed, 22 insertions(+), 12 deletions(-) diff --git a/core/filtermaps/filtermaps.go b/core/filtermaps/filtermaps.go index fe75c1ea7d..dfc20521f6 100644 --- a/core/filtermaps/filtermaps.go +++ b/core/filtermaps/filtermaps.go @@ -70,6 +70,7 @@ type FilterMaps struct { indexLock sync.RWMutex indexedRange filterMapsRange indexedView *ChainView // always consistent with the log index + hasTempRange bool // also accessed by indexer and matcher backend but no locking needed. filterMapCache *lru.Cache[uint32, filterMap] @@ -94,7 +95,7 @@ type FilterMaps struct { ptrTailUnindexMap uint32 targetView *ChainView - matcherSyncRequest *FilterMapsMatcherBackend + matcherSyncRequests []*FilterMapsMatcherBackend historyCutoff uint64 finalBlock, lastFinal uint64 lastFinalEpoch uint32 @@ -330,7 +331,7 @@ func (f *FilterMaps) init() error { fmr.blocks = common.NewRange(cp.BlockNumber+1, 0) fmr.maps = common.NewRange(uint32(bestLen)< Date: Thu, 20 Mar 2025 17:11:40 +0100 Subject: [PATCH 6/7] p2p/discover: repeat WHOAREYOU challenge when handshake in progress (#31356) This fixes the handshake in a scenario where the remote end sends two unknown packets in a row. When this happens, we would previously respond to both with a WHOAREYOU challenge, but keep only the latest sent challenge. Transmission is assumed to be unreliable, so any client that sends two request packets simultaneously has to be prepared to follow up on whichever request leads to a handshake. With this fix, we force them to do the handshake that we can actually complete. Fixes #30581 --- p2p/discover/v5_udp.go | 26 +++++++++++- p2p/discover/v5_udp_test.go | 70 +++++++++++++++++++++++++++++++++ p2p/discover/v5wire/encoding.go | 6 +++ 3 files changed, 100 insertions(+), 2 deletions(-) diff --git a/p2p/discover/v5_udp.go b/p2p/discover/v5_udp.go index 9e849751c1..6f7c797152 100644 --- a/p2p/discover/v5_udp.go +++ b/p2p/discover/v5_udp.go @@ -50,11 +50,20 @@ const ( // encoding/decoding and with the handshake; the UDPv5 object handles higher-level concerns. type codecV5 interface { // Encode encodes a packet. - Encode(enode.ID, string, v5wire.Packet, *v5wire.Whoareyou) ([]byte, v5wire.Nonce, error) + // + // If the underlying type of 'p' is *v5wire.Whoareyou, a Whoareyou challenge packet is + // encoded. If the 'challenge' parameter is non-nil, the packet is encoded as a + // handshake message packet. Otherwise, the packet will be encoded as an ordinary + // message packet. + Encode(id enode.ID, addr string, p v5wire.Packet, challenge *v5wire.Whoareyou) ([]byte, v5wire.Nonce, error) // Decode decodes a packet. It returns a *v5wire.Unknown packet if decryption fails. // The *enode.Node return value is non-nil when the input contains a handshake response. - Decode([]byte, string) (enode.ID, *enode.Node, v5wire.Packet, error) + Decode(b []byte, addr string) (enode.ID, *enode.Node, v5wire.Packet, error) + + // CurrentChallenge returns the most recent WHOAREYOU challenge that was encoded to given node. + // This will return a non-nil value if there is an active handshake attempt with the node, and nil otherwise. + CurrentChallenge(id enode.ID, addr string) *v5wire.Whoareyou } // UDPv5 is the implementation of protocol version 5. @@ -824,6 +833,19 @@ func (t *UDPv5) handle(p v5wire.Packet, fromID enode.ID, fromAddr netip.AddrPort // handleUnknown initiates a handshake by responding with WHOAREYOU. func (t *UDPv5) handleUnknown(p *v5wire.Unknown, fromID enode.ID, fromAddr netip.AddrPort) { + currentChallenge := t.codec.CurrentChallenge(fromID, fromAddr.String()) + if currentChallenge != nil { + // This case happens when the sender issues multiple concurrent requests. + // Since we only support one in-progress handshake at a time, we need to tell + // them which handshake attempt they need to complete. We tell them to use the + // existing handshake attempt since the response to that one might still be in + // transit. + t.log.Debug("Repeating discv5 handshake challenge", "id", fromID, "addr", fromAddr) + t.sendResponse(fromID, fromAddr, currentChallenge) + return + } + + // Send a fresh challenge. challenge := &v5wire.Whoareyou{Nonce: p.Nonce} crand.Read(challenge.IDNonce[:]) if n := t.GetNode(fromID); n != nil { diff --git a/p2p/discover/v5_udp_test.go b/p2p/discover/v5_udp_test.go index 371f414760..3026dff538 100644 --- a/p2p/discover/v5_udp_test.go +++ b/p2p/discover/v5_udp_test.go @@ -140,6 +140,26 @@ func TestUDPv5_unknownPacket(t *testing.T) { test.waitPacketOut(func(p *v5wire.Whoareyou, addr netip.AddrPort, _ v5wire.Nonce) { check(p, 0) }) +} + +func TestUDPv5_unknownPacketKnownNode(t *testing.T) { + t.Parallel() + test := newUDPV5Test(t) + defer test.close() + + nonce := v5wire.Nonce{1, 2, 3} + check := func(p *v5wire.Whoareyou, wantSeq uint64) { + t.Helper() + if p.Nonce != nonce { + t.Error("wrong nonce in WHOAREYOU:", p.Nonce, nonce) + } + if p.IDNonce == ([16]byte{}) { + t.Error("all zero ID nonce") + } + if p.RecordSeq != wantSeq { + t.Errorf("wrong record seq %d in WHOAREYOU, want %d", p.RecordSeq, wantSeq) + } + } // Make node known. n := test.getNode(test.remotekey, test.remoteaddr).Node() @@ -151,6 +171,42 @@ func TestUDPv5_unknownPacket(t *testing.T) { }) } +// This test checks that, when multiple 'unknown' packets are received during a handshake, +// the node sticks to the first handshake attempt. +func TestUDPv5_handshakeRepeatChallenge(t *testing.T) { + t.Parallel() + test := newUDPV5Test(t) + defer test.close() + + nonce1 := v5wire.Nonce{1} + nonce2 := v5wire.Nonce{2} + nonce3 := v5wire.Nonce{3} + check := func(p *v5wire.Whoareyou, wantNonce v5wire.Nonce) { + t.Helper() + if p.Nonce != wantNonce { + t.Error("wrong nonce in WHOAREYOU:", p.Nonce, wantNonce) + } + } + + // Unknown packet from unknown node. + test.packetIn(&v5wire.Unknown{Nonce: nonce1}) + test.waitPacketOut(func(p *v5wire.Whoareyou, addr netip.AddrPort, _ v5wire.Nonce) { + check(p, nonce1) + }) + + // Second unknown packet. Here we expect the response to reference the + // first unknown packet. + test.packetIn(&v5wire.Unknown{Nonce: nonce2}) + test.waitPacketOut(func(p *v5wire.Whoareyou, addr netip.AddrPort, _ v5wire.Nonce) { + check(p, nonce1) + }) + // Third unknown packet. This should still return the first nonce. + test.packetIn(&v5wire.Unknown{Nonce: nonce3}) + test.waitPacketOut(func(p *v5wire.Whoareyou, addr netip.AddrPort, _ v5wire.Nonce) { + check(p, nonce1) + }) +} + // This test checks that incoming FINDNODE calls are handled correctly. func TestUDPv5_findnodeHandling(t *testing.T) { t.Parallel() @@ -698,6 +754,8 @@ type testCodec struct { test *udpV5Test id enode.ID ctr uint64 + + sentChallenges map[enode.ID]*v5wire.Whoareyou } type testCodecFrame struct { @@ -712,11 +770,23 @@ func (c *testCodec) Encode(toID enode.ID, addr string, p v5wire.Packet, _ *v5wir var authTag v5wire.Nonce binary.BigEndian.PutUint64(authTag[:], c.ctr) + if w, ok := p.(*v5wire.Whoareyou); ok { + // Store recently sent Whoareyou challenges. + if c.sentChallenges == nil { + c.sentChallenges = make(map[enode.ID]*v5wire.Whoareyou) + } + c.sentChallenges[toID] = w + } + penc, _ := rlp.EncodeToBytes(p) frame, err := rlp.EncodeToBytes(testCodecFrame{c.id, authTag, p.Kind(), penc}) return frame, authTag, err } +func (c *testCodec) CurrentChallenge(id enode.ID, addr string) *v5wire.Whoareyou { + return c.sentChallenges[id] +} + func (c *testCodec) Decode(input []byte, addr string) (enode.ID, *enode.Node, v5wire.Packet, error) { frame, p, err := c.decodeFrame(input) if err != nil { diff --git a/p2p/discover/v5wire/encoding.go b/p2p/discover/v5wire/encoding.go index 904a3ddec6..e50b7cd16d 100644 --- a/p2p/discover/v5wire/encoding.go +++ b/p2p/discover/v5wire/encoding.go @@ -245,6 +245,12 @@ func (c *Codec) EncodeRaw(id enode.ID, head Header, msgdata []byte) ([]byte, err return c.buf.Bytes(), nil } +// CurrentChallenge returns the latest challenge sent to the given node. +// This will return non-nil while a handshake is in progress. +func (c *Codec) CurrentChallenge(id enode.ID, addr string) *Whoareyou { + return c.sc.getHandshake(id, addr) +} + func (c *Codec) writeHeaders(head *Header) { c.buf.Reset() c.buf.Write(head.IV[:]) From 7fed9584b5426be5db6d7b0198acdec6515d9c81 Mon Sep 17 00:00:00 2001 From: rjl493456442 Date: Fri, 21 Mar 2025 05:05:15 +0800 Subject: [PATCH 7/7] core/txpool/legacypool: reject gapped tx from delegated account (#31430) This pull request improves the protection mechanism in the txpool for senders with delegation. A sender with either delegation or pending delegation is now limited to a maximum of one in-flight executable transaction, while gapped transactions will be rejected. Reason: If nonce-gapped transaction from delegated/pending-delegated senders can be acceptable, then it's no-longer possible to send another "executable" transaction with correct nonce due to the policy of at most one inflight tx. The gapped transaction will be stuck in the txpool, with no meaningful way to unlock the sender. --------- Co-authored-by: lightclient --- core/txpool/legacypool/legacypool.go | 54 ++++++++++++++--------- core/txpool/legacypool/legacypool_test.go | 7 ++- 2 files changed, 38 insertions(+), 23 deletions(-) diff --git a/core/txpool/legacypool/legacypool.go b/core/txpool/legacypool/legacypool.go index 78be81480f..7a0095a5ad 100644 --- a/core/txpool/legacypool/legacypool.go +++ b/core/txpool/legacypool/legacypool.go @@ -67,6 +67,10 @@ var ( // transactions is reached for specific accounts. ErrInflightTxLimitReached = errors.New("in-flight transaction limit reached for delegated accounts") + // ErrOutOfOrderTxFromDelegated is returned when the transaction with gapped + // nonce received from the accounts with delegation or pending delegation. + ErrOutOfOrderTxFromDelegated = errors.New("gapped-nonce tx from delegated accounts") + // ErrAuthorityReserved is returned if a transaction has an authorization // signed by an address which already has in-flight transactions known to the // pool. @@ -606,33 +610,39 @@ func (pool *LegacyPool) validateTx(tx *types.Transaction) error { return pool.validateAuth(tx) } +// checkDelegationLimit determines if the tx sender is delegated or has a +// pending delegation, and if so, ensures they have at most one in-flight +// **executable** transaction, e.g. disallow stacked and gapped transactions +// from the account. +func (pool *LegacyPool) checkDelegationLimit(tx *types.Transaction) error { + from, _ := types.Sender(pool.signer, tx) // validated + + // Short circuit if the sender has neither delegation nor pending delegation. + if pool.currentState.GetCodeHash(from) == types.EmptyCodeHash && len(pool.all.auths[from]) == 0 { + return nil + } + pending := pool.pending[from] + if pending == nil { + // Transaction with gapped nonce is not supported for delegated accounts + if pool.pendingNonces.get(from) != tx.Nonce() { + return ErrOutOfOrderTxFromDelegated + } + return nil + } + // Transaction replacement is supported + if pending.Contains(tx.Nonce()) { + return nil + } + return ErrInflightTxLimitReached +} + // validateAuth verifies that the transaction complies with code authorization // restrictions brought by SetCode transaction type. func (pool *LegacyPool) validateAuth(tx *types.Transaction) error { - from, _ := types.Sender(pool.signer, tx) // validated - // Allow at most one in-flight tx for delegated accounts or those with a // pending authorization. - if pool.currentState.GetCodeHash(from) != types.EmptyCodeHash || len(pool.all.auths[from]) != 0 { - var ( - count int - exists bool - ) - pending := pool.pending[from] - if pending != nil { - count += pending.Len() - exists = pending.Contains(tx.Nonce()) - } - queue := pool.queue[from] - if queue != nil { - count += queue.Len() - exists = exists || queue.Contains(tx.Nonce()) - } - // Replace the existing in-flight transaction for delegated accounts - // are still supported - if count >= 1 && !exists { - return ErrInflightTxLimitReached - } + if err := pool.checkDelegationLimit(tx); err != nil { + return err } // Authorities cannot conflict with any pending or queued transactions. if auths := tx.SetCodeAuthorities(); len(auths) > 0 { diff --git a/core/txpool/legacypool/legacypool_test.go b/core/txpool/legacypool/legacypool_test.go index ef887041ad..3f269bd69e 100644 --- a/core/txpool/legacypool/legacypool_test.go +++ b/core/txpool/legacypool/legacypool_test.go @@ -2262,6 +2262,11 @@ func TestSetCodeTransactions(t *testing.T) { aa := common.Address{0xaa, 0xaa} statedb.SetCode(addrA, append(types.DelegationPrefix, aa.Bytes()...)) statedb.SetCode(aa, []byte{byte(vm.ADDRESS), byte(vm.PUSH0), byte(vm.SSTORE)}) + + // Send gapped transaction, it should be rejected. + if err := pool.addRemoteSync(pricedTransaction(2, 100000, big.NewInt(1), keyA)); !errors.Is(err, ErrOutOfOrderTxFromDelegated) { + t.Fatalf("%s: error mismatch: want %v, have %v", name, ErrOutOfOrderTxFromDelegated, err) + } // Send transactions. First is accepted, second is rejected. if err := pool.addRemoteSync(pricedTransaction(0, 100000, big.NewInt(1), keyA)); err != nil { t.Fatalf("%s: failed to add remote transaction: %v", name, err) @@ -2269,7 +2274,7 @@ func TestSetCodeTransactions(t *testing.T) { if err := pool.addRemoteSync(pricedTransaction(1, 100000, big.NewInt(1), keyA)); !errors.Is(err, ErrInflightTxLimitReached) { t.Fatalf("%s: error mismatch: want %v, have %v", name, ErrInflightTxLimitReached, err) } - // Also check gapped transaction. + // Check gapped transaction again. if err := pool.addRemoteSync(pricedTransaction(2, 100000, big.NewInt(1), keyA)); !errors.Is(err, ErrInflightTxLimitReached) { t.Fatalf("%s: error mismatch: want %v, have %v", name, ErrInflightTxLimitReached, err) }