From 2a2a902aab0bc75734d0dabf5a4324f04439669b Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Tue, 25 Feb 2025 13:38:52 +0100 Subject: [PATCH] cmd/workload: add --sepolia and --mainnet flags --- cmd/workload/filtertest.go | 63 +++++++++++++++------------------- cmd/workload/filtertestgen.go | 57 +++++++++++++++--------------- cmd/workload/filtertestperf.go | 31 ++++++++++++----- cmd/workload/main.go | 2 +- cmd/workload/testsuite.go | 54 +++++++++++++++++++++++++++-- 5 files changed, 129 insertions(+), 78 deletions(-) diff --git a/cmd/workload/filtertest.go b/cmd/workload/filtertest.go index 42ea11ccb3..ae7af44b26 100644 --- a/cmd/workload/filtertest.go +++ b/cmd/workload/filtertest.go @@ -21,7 +21,6 @@ import ( "encoding/json" "fmt" "math/big" - "os" "time" "github.com/ethereum/go-ethereum" @@ -32,17 +31,18 @@ import ( "github.com/ethereum/go-ethereum/internal/utesting" "github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/rpc" - "github.com/urfave/cli/v2" ) type filterTestSuite struct { - ec *ethclient.Client - filterTest + cfg testConfig + queries [][]*filterQuery } -func newFilterTestSuite(ctx *cli.Context) *filterTestSuite { - s := &filterTestSuite{ec: makeEthClient(ctx)} - s.filterTest.initFilterTest(ctx) +func newFilterTestSuite(cfg testConfig) *filterTestSuite { + s := &filterTestSuite{cfg: cfg} + if err := s.loadQueries(); err != nil { + exit(err) + } return s } @@ -54,24 +54,9 @@ func (s *filterTestSuite) allTests() []utesting.Test { } } -type filterTest struct { - filterQueryFile, filterErrorFile string - filterQueries [filterBuckets][]*filterQuery - filterQueriesLoaded bool - filterErrors []*filterQuery -} - -func (f *filterTest) initFilterTest(ctx *cli.Context) { - f.filterQueryFile = ctx.String(filterQueryFileFlag.Name) - f.filterErrorFile = ctx.String(filterErrorFileFlag.Name) -} - func (s *filterTestSuite) filterRange(t *utesting.T, test func(query *filterQuery) bool, do func(t *utesting.T, query *filterQuery)) { - if !s.filterQueriesLoaded { - s.loadQueries() - } var count, total int - for _, bucket := range s.filterQueries { + for _, bucket := range s.queries { for _, query := range bucket { if test(query) { total++ @@ -83,7 +68,7 @@ func (s *filterTestSuite) filterRange(t *utesting.T, test func(query *filterQuer } start := time.Now() last := start - for _, bucket := range s.filterQueries { + for _, bucket := range s.queries { for _, query := range bucket { if test(query) { do(t, query) @@ -117,14 +102,14 @@ func (s *filterTestSuite) filterLongRange(t *utesting.T) { // filterFullRange runs all filter tests, extending their range from genesis up // to the latest block. Note that results are only partially verified in this mode. func (s *filterTestSuite) filterFullRange(t *utesting.T) { - finalized := mustGetFinalizedBlock(s.ec) + finalized := mustGetFinalizedBlock(s.cfg.client) s.filterRange(t, func(query *filterQuery) bool { return query.ToBlock+1-query.FromBlock > finalized/2 }, s.fullRangeQueryAndCheck) } func (s *filterTestSuite) queryAndCheck(t *utesting.T, query *filterQuery) { - query.run(s.ec) + query.run(s.cfg.client) 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 @@ -141,7 +126,7 @@ func (s *filterTestSuite) fullRangeQueryAndCheck(t *utesting.T, query *filterQue Address: query.Address, Topics: query.Topics, } - frQuery.run(s.ec) + frQuery.run(s.cfg.client) if frQuery.Err != nil { t.Errorf("Full range filter query failed (addresses: %v topics: %v error: %v)", frQuery.Address, frQuery.Topics, frQuery.Err) return @@ -160,21 +145,27 @@ func (s *filterTestSuite) fullRangeQueryAndCheck(t *utesting.T, query *filterQue } } -func (s *filterTestSuite) loadQueries() int { - file, err := os.Open(s.filterQueryFile) +func (s *filterTestSuite) loadQueries() error { + file, err := s.cfg.fsys.Open(s.cfg.filterQueryFile) if err != nil { - fmt.Println("Error opening filter test query file:", err) - return 0 + return fmt.Errorf("can't open filterQueryFile: %v", err) + } + defer file.Close() + + var queries [][]*filterQuery + if err := json.NewDecoder(file).Decode(&queries); err != nil { + return fmt.Errorf("invalid JSON in %s: %v", s.cfg.filterQueryFile, err) } - json.NewDecoder(file).Decode(&s.filterQueries) - file.Close() var count int - for _, bucket := range s.filterQueries { + for _, bucket := range queries { count += len(bucket) } + if count == 0 { + return fmt.Errorf("filterQueryFile %s is empty", s.cfg.filterQueryFile) + } fmt.Println("Loaded", count, "filter test queries") - s.filterQueriesLoaded = true - return count + s.queries = queries + return nil } // filterQuery is a single query for testing. diff --git a/cmd/workload/filtertestgen.go b/cmd/workload/filtertestgen.go index 6c765320a3..3de1ea2bc8 100644 --- a/cmd/workload/filtertestgen.go +++ b/cmd/workload/filtertestgen.go @@ -52,16 +52,6 @@ var ( filterErrorFileFlag, }, } - filterPerfCommand = &cli.Command{ - Name: "performance", - Usage: "Runs log filter performance test against an RPC endpoint", - ArgsUsage: "", - Action: filterPerfCmd, - Flags: []cli.Flag{ - filterQueryFileFlag, - filterErrorFileFlag, - }, - } filterQueryFileFlag = &cli.StringFlag{ Name: "queries", Usage: "JSON file containing filter test queries", @@ -91,7 +81,7 @@ func filterGenCmd(ctx *cli.Context) error { query := f.newQuery() query.run(f.ec) if query.Err != nil { - f.filterErrors = append(f.filterErrors, query) + f.errors = append(f.errors, query) continue } if len(query.results) > 0 && len(query.results) <= maxFilterResultSize { @@ -109,7 +99,7 @@ func filterGenCmd(ctx *cli.Context) error { ) } if extQuery.Err != nil { - f.filterErrors = append(f.filterErrors, extQuery) + f.errors = append(f.errors, extQuery) break } if len(extQuery.results) > maxFilterResultSize { @@ -127,16 +117,23 @@ func filterGenCmd(ctx *cli.Context) error { } } +// filterTestGen is the filter query test generator. type filterTestGen struct { - ec *ethclient.Client + ec *ethclient.Client + queryFile string + errorFile string + finalizedBlock int64 - filterTest + queries [filterBuckets][]*filterQuery + errors []*filterQuery } func newFilterTestGen(ctx *cli.Context) *filterTestGen { - s := &filterTestGen{ec: makeEthClient(ctx)} - s.filterTest.initFilterTest(ctx) - return s + return &filterTestGen{ + ec: makeEthClient(ctx), + queryFile: ctx.String(filterQueryFileFlag.Name), + errorFile: ctx.String(filterErrorFileFlag.Name), + } } func (s *filterTestGen) updateFinalizedBlock() { @@ -162,13 +159,13 @@ func (s *filterTestGen) storeQuery(query *filterQuery) { if bucket >= filterBuckets { bucket = filterBuckets - 1 } - if len(s.filterQueries[bucket]) < maxFilterBucketSize { - s.filterQueries[bucket] = append(s.filterQueries[bucket], query) + if len(s.queries[bucket]) < maxFilterBucketSize { + s.queries[bucket] = append(s.queries[bucket], query) } else { - s.filterQueries[bucket][rand.Intn(len(s.filterQueries[bucket]))] = query + s.queries[bucket][rand.Intn(len(s.queries[bucket]))] = query } fmt.Print("Generated queries per bucket:") - for _, list := range s.filterQueries { + for _, list := range s.queries { fmt.Print(" ", len(list)) } fmt.Println() @@ -340,7 +337,7 @@ func (s *filterTestGen) newNarrowedQuery() *filterQuery { // randomQuery returns a random query from the ones that were already generated. func (s *filterTestGen) randomQuery() *filterQuery { var bucket, bucketCount int - for _, list := range s.filterQueries { + for _, list := range s.queries { if len(list) > 0 { bucketCount++ } @@ -349,7 +346,7 @@ func (s *filterTestGen) randomQuery() *filterQuery { return nil } pick := rand.Intn(bucketCount) - for i, list := range s.filterQueries { + for i, list := range s.queries { if len(list) > 0 { if pick == 0 { bucket = i @@ -358,28 +355,28 @@ func (s *filterTestGen) randomQuery() *filterQuery { pick-- } } - return s.filterQueries[bucket][rand.Intn(len(s.filterQueries[bucket]))] + return s.queries[bucket][rand.Intn(len(s.queries[bucket]))] } // writeQueries serializes the generated queries to the output file. func (s *filterTestGen) writeQueries() { - file, err := os.Create(s.filterQueryFile) + file, err := os.Create(s.queryFile) if err != nil { - exit(fmt.Errorf("Error creating filter test query file %s: %v", s.filterQueryFile, err)) + exit(fmt.Errorf("Error creating filter test query file %s: %v", s.queryFile, err)) return } - json.NewEncoder(file).Encode(&s.filterQueries) + json.NewEncoder(file).Encode(&s.queries) file.Close() } // writeQueries serializes the generated errors to the error file. func (f *filterTestGen) writeErrors() { - file, err := os.Create(f.filterErrorFile) + file, err := os.Create(f.errorFile) if err != nil { - exit(fmt.Errorf("Error creating filter error file %s: %v", f.filterErrorFile, err)) + exit(fmt.Errorf("Error creating filter error file %s: %v", f.errorFile, err)) return } - json.NewEncoder(file).Encode(f.filterErrors) + json.NewEncoder(file).Encode(f.errors) file.Close() } diff --git a/cmd/workload/filtertestperf.go b/cmd/workload/filtertestperf.go index 41d0dbeb81..71bbbd171c 100644 --- a/cmd/workload/filtertestperf.go +++ b/cmd/workload/filtertestperf.go @@ -19,19 +19,33 @@ package main import ( "fmt" "math/rand" + "slices" "sort" "time" "github.com/urfave/cli/v2" ) +var ( + filterPerfCommand = &cli.Command{ + Name: "performance", + Usage: "Runs log filter performance test against an RPC endpoint", + ArgsUsage: "", + Action: filterPerfCmd, + Flags: []cli.Flag{ + testSepoliaFlag, + testMainnetFlag, + filterQueryFileFlag, + filterErrorFileFlag, + }, + } +) + const passCount = 1 func filterPerfCmd(ctx *cli.Context) error { - f := newFilterTestSuite(ctx) - if f.loadQueries() == 0 { - exit("No test requests loaded") - } + cfg := testConfigFromCLI(ctx) + f := newFilterTestSuite(cfg) type queryTest struct { query *filterQuery @@ -40,7 +54,7 @@ func filterPerfCmd(ctx *cli.Context) error { medianTime time.Duration } var queries, processed []queryTest - for i, bucket := range f.filterQueries[:] { + for i, bucket := range f.queries[:] { for j, query := range bucket { queries = append(queries, queryTest{query: query, bucket: i, index: j}) } @@ -56,9 +70,9 @@ func filterPerfCmd(ctx *cli.Context) error { queries[pick] = queries[len(queries)-1] queries = queries[:len(queries)-1] start := time.Now() - qt.query.run(f.ec) + qt.query.run(cfg.client) qt.runtime = append(qt.runtime, time.Since(start)) - sort.Slice(qt.runtime, func(i, j int) bool { return qt.runtime[i] < qt.runtime[j] }) + slices.Sort(qt.runtime) qt.medianTime = qt.runtime[len(qt.runtime)/2] if qt.query.Err != nil { failed++ @@ -78,7 +92,7 @@ func filterPerfCmd(ctx *cli.Context) error { // Show results and stats. fmt.Println("Performance test finished; processed:", len(queries), "failed:", failed, "result mismatch:", mismatch) - stats := make([]bucketStats, len(f.filterQueries)) + stats := make([]bucketStats, len(f.queries)) var wildcardStats bucketStats for _, qt := range queries { bs := &stats[qt.bucket] @@ -120,5 +134,4 @@ 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)) - } diff --git a/cmd/workload/main.go b/cmd/workload/main.go index fbde43bbb2..a4646164a7 100644 --- a/cmd/workload/main.go +++ b/cmd/workload/main.go @@ -66,7 +66,7 @@ func makeEthClient(ctx *cli.Context) *ethclient.Client { return cl } -func exit(err interface{}) { +func exit(err any) { if err == nil { os.Exit(0) } diff --git a/cmd/workload/testsuite.go b/cmd/workload/testsuite.go index 56c8f1ff84..99c2d2bf73 100644 --- a/cmd/workload/testsuite.go +++ b/cmd/workload/testsuite.go @@ -17,14 +17,20 @@ package main import ( + "embed" + "io/fs" "os" + "github.com/ethereum/go-ethereum/ethclient" "github.com/ethereum/go-ethereum/internal/flags" "github.com/ethereum/go-ethereum/internal/utesting" "github.com/ethereum/go-ethereum/log" "github.com/urfave/cli/v2" ) +//go:embed queries +var builtinTestFiles embed.FS + var ( runTestCommand = &cli.Command{ Name: "test", @@ -34,8 +40,9 @@ var ( Flags: []cli.Flag{ testPatternFlag, testTAPFlag, + testSepoliaFlag, + testMainnetFlag, filterQueryFileFlag, - filterErrorFileFlag, }, } testPatternFlag = &cli.StringFlag{ @@ -48,10 +55,53 @@ var ( Usage: "Output test results in TAP format", Category: flags.TestingCategory, } + testSepoliaFlag = &cli.BoolFlag{ + Name: "sepolia", + Usage: "Use test cases for sepolia network", + Category: flags.TestingCategory, + } + testMainnetFlag = &cli.BoolFlag{ + Name: "mainnet", + Usage: "Use test cases for mainnet network", + Category: flags.TestingCategory, + } ) +// testConfig holds the parameters for testing. +type testConfig struct { + client *ethclient.Client + fsys fs.FS + filterQueryFile string +} + +func testConfigFromCLI(ctx *cli.Context) (cfg testConfig) { + flags.CheckExclusive(ctx, testMainnetFlag, testSepoliaFlag) + if (ctx.IsSet(testMainnetFlag.Name) || ctx.IsSet(testSepoliaFlag.Name)) && ctx.IsSet(filterQueryFileFlag.Name) { + exit(filterQueryFileFlag.Name + " cannot be used with " + testMainnetFlag.Name + " or " + testSepoliaFlag.Name) + } + + // configure ethclient + cfg.client = makeEthClient(ctx) + + // configure test files + switch { + case ctx.Bool(testMainnetFlag.Name): + cfg.fsys = builtinTestFiles + cfg.filterQueryFile = "queries/filter_queries_mainnet.json" + case ctx.Bool(testSepoliaFlag.Name): + cfg.fsys = builtinTestFiles + cfg.filterQueryFile = "queries/filter_queries_sepolia.json" + default: + cfg.fsys = os.DirFS(".") + cfg.filterQueryFile = ctx.String(filterQueryFileFlag.Name) + } + + return cfg +} + func runTestCmd(ctx *cli.Context) error { - s := newFilterTestSuite(ctx) + cfg := testConfigFromCLI(ctx) + s := newFilterTestSuite(cfg) // Filter test cases. tests := s.allTests()