mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-07-27 23:26:44 +00:00
cmd/workload: add history test generator
This commit is contained in:
parent
f6723bfb11
commit
9a7d8cd216
5 changed files with 198 additions and 18 deletions
|
|
@ -27,7 +27,6 @@ import (
|
|||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/ethclient"
|
||||
"github.com/ethereum/go-ethereum/internal/utesting"
|
||||
"github.com/ethereum/go-ethereum/rlp"
|
||||
"github.com/ethereum/go-ethereum/rpc"
|
||||
|
|
@ -199,10 +198,10 @@ func (fq *filterQuery) calculateHash() common.Hash {
|
|||
return crypto.Keccak256Hash(enc)
|
||||
}
|
||||
|
||||
func (fq *filterQuery) run(ec *ethclient.Client) {
|
||||
func (fq *filterQuery) run(client *client) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second*30)
|
||||
defer cancel()
|
||||
logs, err := ec.FilterLogs(ctx, ethereum.FilterQuery{
|
||||
logs, err := client.Eth.FilterLogs(ctx, ethereum.FilterQuery{
|
||||
FromBlock: big.NewInt(fq.FromBlock),
|
||||
ToBlock: big.NewInt(fq.ToBlock),
|
||||
Addresses: fq.Address,
|
||||
|
|
|
|||
|
|
@ -27,7 +27,6 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/ethclient"
|
||||
"github.com/ethereum/go-ethereum/internal/flags"
|
||||
"github.com/ethereum/go-ethereum/rpc"
|
||||
"github.com/urfave/cli/v2"
|
||||
|
|
@ -79,7 +78,7 @@ func filterGenCmd(ctx *cli.Context) error {
|
|||
|
||||
f.updateFinalizedBlock()
|
||||
query := f.newQuery()
|
||||
query.run(f.ec)
|
||||
query.run(f.client)
|
||||
if query.Err != nil {
|
||||
f.errors = append(f.errors, query)
|
||||
continue
|
||||
|
|
@ -90,7 +89,7 @@ func filterGenCmd(ctx *cli.Context) error {
|
|||
if extQuery == nil {
|
||||
break
|
||||
}
|
||||
extQuery.run(f.ec)
|
||||
extQuery.run(f.client)
|
||||
if extQuery.Err == nil && len(extQuery.results) < len(query.results) {
|
||||
extQuery.Err = fmt.Errorf("invalid result length; old range %d %d; old length %d; new range %d %d; new length %d; address %v; Topics %v",
|
||||
query.FromBlock, query.ToBlock, len(query.results),
|
||||
|
|
@ -119,7 +118,7 @@ func filterGenCmd(ctx *cli.Context) error {
|
|||
|
||||
// filterTestGen is the filter query test generator.
|
||||
type filterTestGen struct {
|
||||
ec *ethclient.Client
|
||||
client *client
|
||||
queryFile string
|
||||
errorFile string
|
||||
|
||||
|
|
@ -130,14 +129,14 @@ type filterTestGen struct {
|
|||
|
||||
func newFilterTestGen(ctx *cli.Context) *filterTestGen {
|
||||
return &filterTestGen{
|
||||
ec: makeEthClient(ctx),
|
||||
client: makeClient(ctx),
|
||||
queryFile: ctx.String(filterQueryFileFlag.Name),
|
||||
errorFile: ctx.String(filterErrorFileFlag.Name),
|
||||
}
|
||||
}
|
||||
|
||||
func (s *filterTestGen) updateFinalizedBlock() {
|
||||
s.finalizedBlock = mustGetFinalizedBlock(s.ec)
|
||||
s.finalizedBlock = mustGetFinalizedBlock(s.client)
|
||||
}
|
||||
|
||||
const (
|
||||
|
|
@ -380,10 +379,10 @@ func (s *filterTestGen) writeErrors() {
|
|||
json.NewEncoder(file).Encode(s.errors)
|
||||
}
|
||||
|
||||
func mustGetFinalizedBlock(ec *ethclient.Client) int64 {
|
||||
func mustGetFinalizedBlock(client *client) int64 {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
|
||||
defer cancel()
|
||||
header, err := ec.HeaderByNumber(ctx, big.NewInt(int64(rpc.FinalizedBlockNumber)))
|
||||
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))
|
||||
}
|
||||
|
|
|
|||
170
cmd/workload/historytestgen.go
Normal file
170
cmd/workload/historytestgen.go
Normal file
|
|
@ -0,0 +1,170 @@
|
|||
// 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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"os"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/internal/flags"
|
||||
"github.com/ethereum/go-ethereum/rlp"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
// eth_getBlockByHash
|
||||
// eth_getBlockByNumber
|
||||
// eth_getBlockReceipts
|
||||
// eth_getBlockTransactionCountByHash
|
||||
// eth_getBlockTransactionCountByNumber
|
||||
// eth_getTransactionByBlockHashAndIndex
|
||||
// eth_getTransactionByBlockNumberAndIndex
|
||||
|
||||
var (
|
||||
historyGenerateCommand = &cli.Command{
|
||||
Name: "historygen",
|
||||
Usage: "Generates history retrieval tests",
|
||||
ArgsUsage: "<RPC endpoint URL>",
|
||||
Action: generateHistoryTests,
|
||||
Flags: []cli.Flag{
|
||||
historyTestFileFlag,
|
||||
},
|
||||
}
|
||||
|
||||
historyTestFileFlag = &cli.StringFlag{
|
||||
Name: "history-tests",
|
||||
Usage: "JSON file containing filter test queries",
|
||||
Value: "history_tests.json",
|
||||
Category: flags.TestingCategory,
|
||||
}
|
||||
historyTestEarliesFlag = &cli.IntFlag{
|
||||
Name: "earliest",
|
||||
Usage: "JSON file containing filter test queries",
|
||||
Value: 0,
|
||||
Category: flags.TestingCategory,
|
||||
}
|
||||
)
|
||||
|
||||
// historyTest is the content of a history test.
|
||||
type historyTest struct {
|
||||
BlockNumbers []uint64 `json:"blockNumbers"`
|
||||
BlockHashes []common.Hash `json:"blockHashes"`
|
||||
TxCounts []int `json:"txCounts"`
|
||||
TxHashIndex []int `json:"txHashIndex"`
|
||||
TxHashes []*common.Hash `json:"txHashes"`
|
||||
ReceiptsHashes []common.Hash `json:"blockReceiptsHashes"`
|
||||
}
|
||||
|
||||
const historyTestBlockCount = 2000
|
||||
|
||||
func generateHistoryTests(clictx *cli.Context) error {
|
||||
var (
|
||||
client = makeClient(clictx)
|
||||
earliest = uint64(clictx.Int(historyTestEarliesFlag.Name))
|
||||
outputFile = clictx.String(historyTestFileFlag.Name)
|
||||
ctx = context.Background()
|
||||
)
|
||||
|
||||
test := new(historyTest)
|
||||
|
||||
// Create the block numbers. Here we choose 1k blocks between earliest and head.
|
||||
latest, err := client.Eth.BlockNumber(ctx)
|
||||
if err != nil {
|
||||
exit(err)
|
||||
}
|
||||
if latest < historyTestBlockCount {
|
||||
exit(fmt.Errorf("node seems not synced, latest block is %d", latest))
|
||||
}
|
||||
test.BlockNumbers = make([]uint64, 0, historyTestBlockCount)
|
||||
stride := (latest - earliest) / historyTestBlockCount
|
||||
for b := earliest; b < latest; b += stride {
|
||||
test.BlockNumbers = append(test.BlockNumbers, b)
|
||||
}
|
||||
|
||||
// Get blocks and assign block info into the test
|
||||
fmt.Println("Fetching blocks")
|
||||
blocks := make([]*types.Block, len(test.BlockNumbers))
|
||||
for i, blocknum := range test.BlockNumbers {
|
||||
b, err := client.Eth.BlockByNumber(ctx, new(big.Int).SetUint64(blocknum))
|
||||
if err != nil {
|
||||
exit(fmt.Errorf("error fetching block %d: %v", blocknum, err))
|
||||
}
|
||||
blocks[i] = b
|
||||
}
|
||||
test.BlockHashes = make([]common.Hash, len(blocks))
|
||||
test.TxCounts = make([]int, len(blocks))
|
||||
for i, block := range blocks {
|
||||
test.BlockHashes[i] = block.Hash()
|
||||
test.TxCounts[i] = len(block.Transactions())
|
||||
}
|
||||
|
||||
// Fill tx index.
|
||||
test.TxHashIndex = make([]int, len(blocks))
|
||||
test.TxHashes = make([]*common.Hash, len(blocks))
|
||||
for i, block := range blocks {
|
||||
txs := block.Transactions()
|
||||
if len(txs) == 0 {
|
||||
continue
|
||||
}
|
||||
index := len(txs) / 2
|
||||
txhash := txs[index].Hash()
|
||||
test.TxHashIndex[i] = index
|
||||
test.TxHashes[i] = &txhash
|
||||
}
|
||||
|
||||
// Get receipts.
|
||||
fmt.Println("Fetching receipts")
|
||||
test.ReceiptsHashes = make([]common.Hash, len(blocks))
|
||||
for i, blockHash := range test.BlockHashes {
|
||||
receipts, err := client.getBlockReceipts(ctx, blockHash)
|
||||
if err != nil {
|
||||
exit(fmt.Errorf("error fetching block %v receipts: %v", blockHash, err))
|
||||
}
|
||||
test.ReceiptsHashes[i] = calcReceiptsHash(receipts)
|
||||
}
|
||||
|
||||
// Write output file.
|
||||
writeJSON(outputFile, test)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *client) getBlockReceipts(ctx context.Context, arg any) ([]*types.Receipt, error) {
|
||||
var result []*types.Receipt
|
||||
err := c.RPC.CallContext(ctx, &result, "eth_getBlockReceipts", arg)
|
||||
return result, err
|
||||
}
|
||||
|
||||
func calcReceiptsHash(rcpt []*types.Receipt) common.Hash {
|
||||
h := crypto.NewKeccakState()
|
||||
rlp.Encode(h, rcpt)
|
||||
return common.Hash(h.Sum(nil))
|
||||
}
|
||||
|
||||
func writeJSON(fileName string, value any) {
|
||||
file, err := os.Create(fileName)
|
||||
if err != nil {
|
||||
exit(fmt.Errorf("Error creating %s: %v", fileName, err))
|
||||
return
|
||||
}
|
||||
defer file.Close()
|
||||
json.NewEncoder(file).Encode(value)
|
||||
}
|
||||
|
|
@ -23,6 +23,7 @@ import (
|
|||
"github.com/ethereum/go-ethereum/ethclient"
|
||||
"github.com/ethereum/go-ethereum/internal/debug"
|
||||
"github.com/ethereum/go-ethereum/internal/flags"
|
||||
"github.com/ethereum/go-ethereum/rpc"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
|
|
@ -46,6 +47,7 @@ func init() {
|
|||
// Add subcommands.
|
||||
app.Commands = []*cli.Command{
|
||||
runTestCommand,
|
||||
historyGenerateCommand,
|
||||
filterCommand,
|
||||
}
|
||||
}
|
||||
|
|
@ -54,16 +56,24 @@ func main() {
|
|||
exit(app.Run(os.Args))
|
||||
}
|
||||
|
||||
func makeEthClient(ctx *cli.Context) *ethclient.Client {
|
||||
type client struct {
|
||||
Eth *ethclient.Client
|
||||
RPC *rpc.Client
|
||||
}
|
||||
|
||||
func makeClient(ctx *cli.Context) *client {
|
||||
if ctx.NArg() < 1 {
|
||||
exit("missing RPC endpoint URL as command-line argument")
|
||||
}
|
||||
url := ctx.Args().First()
|
||||
cl, err := ethclient.Dial(url)
|
||||
cl, err := rpc.Dial(url)
|
||||
if err != nil {
|
||||
exit(fmt.Errorf("Could not create RPC client at %s: %v", url, err))
|
||||
}
|
||||
return cl
|
||||
return &client{
|
||||
RPC: cl,
|
||||
Eth: ethclient.NewClient(cl),
|
||||
}
|
||||
}
|
||||
|
||||
func exit(err any) {
|
||||
|
|
|
|||
|
|
@ -21,7 +21,6 @@ import (
|
|||
"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"
|
||||
|
|
@ -69,9 +68,10 @@ var (
|
|||
|
||||
// testConfig holds the parameters for testing.
|
||||
type testConfig struct {
|
||||
client *ethclient.Client
|
||||
client *client
|
||||
fsys fs.FS
|
||||
filterQueryFile string
|
||||
historyTestFile string
|
||||
}
|
||||
|
||||
func testConfigFromCLI(ctx *cli.Context) (cfg testConfig) {
|
||||
|
|
@ -81,21 +81,23 @@ func testConfigFromCLI(ctx *cli.Context) (cfg testConfig) {
|
|||
}
|
||||
|
||||
// configure ethclient
|
||||
cfg.client = makeEthClient(ctx)
|
||||
cfg.client = makeClient(ctx)
|
||||
|
||||
// configure test files
|
||||
switch {
|
||||
case ctx.Bool(testMainnetFlag.Name):
|
||||
cfg.fsys = builtinTestFiles
|
||||
cfg.filterQueryFile = "queries/filter_queries_mainnet.json"
|
||||
cfg.historyTestFile = "queries/history_mainnet.json"
|
||||
case ctx.Bool(testSepoliaFlag.Name):
|
||||
cfg.fsys = builtinTestFiles
|
||||
cfg.filterQueryFile = "queries/filter_queries_sepolia.json"
|
||||
cfg.historyTestFile = "queries/history_sepolia.json"
|
||||
default:
|
||||
cfg.fsys = os.DirFS(".")
|
||||
cfg.filterQueryFile = ctx.String(filterQueryFileFlag.Name)
|
||||
cfg.historyTestFile = ctx.String(historyTestFileFlag.Name)
|
||||
}
|
||||
|
||||
return cfg
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue