Skip to content

Commit 670313a

Browse files
committed
add filter tx bench
1 parent c92c7d8 commit 670313a

File tree

1 file changed

+297
-0
lines changed

1 file changed

+297
-0
lines changed

execution/evm/filter_bench_test.go

Lines changed: 297 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,297 @@
1+
package evm
2+
3+
import (
4+
"context"
5+
"crypto/rand"
6+
"fmt"
7+
"math/big"
8+
"testing"
9+
10+
"github.com/ethereum/go-ethereum/common"
11+
"github.com/ethereum/go-ethereum/core/types"
12+
"github.com/ethereum/go-ethereum/crypto"
13+
ds "github.com/ipfs/go-datastore"
14+
dssync "github.com/ipfs/go-datastore/sync"
15+
)
16+
17+
const (
18+
benchPrivateKey = "cece4f25ac74deb1468965160c7185e07dff413f23fcadb611b05ca37ab0a52e"
19+
benchToAddress = "0x944fDcD1c868E3cC566C78023CcB38A32cDA836E"
20+
benchChainID = "1234"
21+
)
22+
23+
// createBenchClient creates a minimal EngineClient for benchmarking FilterTxs.
24+
// It only needs the logger to be set for the FilterTxs method.
25+
func createBenchClient(b *testing.B) *EngineClient {
26+
b.Helper()
27+
baseStore := dssync.MutexWrap(ds.NewMapDatastore())
28+
store := NewEVMStore(baseStore)
29+
client := &EngineClient{
30+
store: store,
31+
}
32+
return client
33+
}
34+
35+
// generateSignedTransaction creates a valid signed Ethereum transaction for benchmarking.
36+
func generateSignedTransaction(b *testing.B, nonce uint64, gasLimit uint64) []byte {
37+
b.Helper()
38+
39+
privateKey, err := crypto.HexToECDSA(benchPrivateKey)
40+
if err != nil {
41+
b.Fatalf("failed to parse private key: %v", err)
42+
}
43+
44+
chainID, ok := new(big.Int).SetString(benchChainID, 10)
45+
if !ok {
46+
b.Fatalf("failed to parse chain ID")
47+
}
48+
49+
toAddress := common.HexToAddress(benchToAddress)
50+
txValue := big.NewInt(1000000000000000000)
51+
gasPrice := big.NewInt(30000000000)
52+
53+
data := make([]byte, 16)
54+
if _, err := rand.Read(data); err != nil {
55+
b.Fatalf("failed to generate random data: %v", err)
56+
}
57+
58+
tx := types.NewTx(&types.LegacyTx{
59+
Nonce: nonce,
60+
To: &toAddress,
61+
Value: txValue,
62+
Gas: gasLimit,
63+
GasPrice: gasPrice,
64+
Data: data,
65+
})
66+
67+
signedTx, err := types.SignTx(tx, types.NewEIP155Signer(chainID), privateKey)
68+
if err != nil {
69+
b.Fatalf("failed to sign transaction: %v", err)
70+
}
71+
72+
txBytes, err := signedTx.MarshalBinary()
73+
if err != nil {
74+
b.Fatalf("failed to marshal transaction: %v", err)
75+
}
76+
77+
return txBytes
78+
}
79+
80+
// generateTransactionBatch creates a batch of signed transactions for benchmarking.
81+
func generateTransactionBatch(b *testing.B, count int, gasLimit uint64) [][]byte {
82+
b.Helper()
83+
txs := make([][]byte, count)
84+
for i := 0; i < count; i++ {
85+
txs[i] = generateSignedTransaction(b, uint64(i), gasLimit)
86+
}
87+
return txs
88+
}
89+
90+
// generateMixedTransactionBatch creates a batch with some valid and some invalid transactions.
91+
// forcedRatio is the percentage of transactions that are "forced" (could include invalid ones).
92+
func generateMixedTransactionBatch(b *testing.B, count int, gasLimit uint64, includeInvalid bool) [][]byte {
93+
b.Helper()
94+
txs := make([][]byte, count)
95+
for i := 0; i < count; i++ {
96+
if includeInvalid && i%10 == 0 {
97+
// Every 10th transaction is invalid (random garbage)
98+
txs[i] = make([]byte, 100)
99+
if _, err := rand.Read(txs[i]); err != nil {
100+
b.Fatalf("failed to generate random data: %v", err)
101+
}
102+
} else {
103+
txs[i] = generateSignedTransaction(b, uint64(i), gasLimit)
104+
}
105+
}
106+
return txs
107+
}
108+
109+
func benchName(n int) string {
110+
if n >= 1000 {
111+
return fmt.Sprintf("%dk", n/1000)
112+
}
113+
return fmt.Sprintf("%d", n)
114+
}
115+
116+
// BenchmarkFilterTxs_OnlyNormalTxs benchmarks FilterTxs when hasForceIncludedTransaction is false.
117+
// In this case, UnmarshalBinary is NOT called - mempool transactions are already validated.
118+
func BenchmarkFilterTxs_OnlyNormalTxs(b *testing.B) {
119+
client := createBenchClient(b)
120+
ctx := context.Background()
121+
122+
txCounts := []int{100, 1000, 10000}
123+
124+
for _, count := range txCounts {
125+
b.Run(benchName(count), func(b *testing.B) {
126+
txs := generateTransactionBatch(b, count, 21000)
127+
128+
b.ResetTimer()
129+
b.ReportAllocs()
130+
131+
for b.Loop() {
132+
// hasForceIncludedTransaction=false means UnmarshalBinary is skipped
133+
_, err := client.FilterTxs(ctx, txs, 0, 0, false)
134+
if err != nil {
135+
b.Fatalf("FilterTxs failed: %v", err)
136+
}
137+
}
138+
})
139+
}
140+
}
141+
142+
// BenchmarkFilterTxs_WithForcedTxs benchmarks FilterTxs when hasForceIncludedTransaction is true.
143+
// In this case, UnmarshalBinary IS called for every transaction to validate and extract gas.
144+
func BenchmarkFilterTxs_WithForcedTxs(b *testing.B) {
145+
client := createBenchClient(b)
146+
ctx := context.Background()
147+
148+
txCounts := []int{100, 1000, 10000}
149+
150+
for _, count := range txCounts {
151+
b.Run(benchName(count), func(b *testing.B) {
152+
txs := generateTransactionBatch(b, count, 21000)
153+
154+
b.ResetTimer()
155+
b.ReportAllocs()
156+
157+
for b.Loop() {
158+
// hasForceIncludedTransaction=true triggers UnmarshalBinary path
159+
_, err := client.FilterTxs(ctx, txs, 0, 0, true)
160+
if err != nil {
161+
b.Fatalf("FilterTxs failed: %v", err)
162+
}
163+
}
164+
})
165+
}
166+
}
167+
168+
// BenchmarkFilterTxs_MixedWithInvalidTxs benchmarks FilterTxs with a mix of valid and invalid transactions.
169+
// This tests the UnmarshalBinary error handling path.
170+
func BenchmarkFilterTxs_MixedWithInvalidTxs(b *testing.B) {
171+
client := createBenchClient(b)
172+
ctx := context.Background()
173+
174+
txCounts := []int{100, 1000, 10000}
175+
176+
for _, count := range txCounts {
177+
b.Run(benchName(count), func(b *testing.B) {
178+
// Generate batch with 10% invalid transactions
179+
txs := generateMixedTransactionBatch(b, count, 21000, true)
180+
181+
b.ResetTimer()
182+
b.ReportAllocs()
183+
184+
for b.Loop() {
185+
// hasForceIncludedTransaction=true triggers UnmarshalBinary path
186+
_, err := client.FilterTxs(ctx, txs, 0, 0, true)
187+
if err != nil {
188+
b.Fatalf("FilterTxs failed: %v", err)
189+
}
190+
}
191+
})
192+
}
193+
}
194+
195+
// BenchmarkFilterTxs_WithGasLimit benchmarks FilterTxs with gas limit enforcement.
196+
// This tests the full path including gas accumulation logic.
197+
func BenchmarkFilterTxs_WithGasLimit(b *testing.B) {
198+
client := createBenchClient(b)
199+
ctx := context.Background()
200+
201+
txCounts := []int{100, 1000}
202+
// Gas limit that allows roughly half the transactions
203+
maxGas := uint64(21000 * 500)
204+
205+
for _, count := range txCounts {
206+
b.Run(benchName(count), func(b *testing.B) {
207+
txs := generateTransactionBatch(b, count, 21000)
208+
209+
b.ResetTimer()
210+
b.ReportAllocs()
211+
212+
for b.Loop() {
213+
// With gas limit and forced transactions
214+
_, err := client.FilterTxs(ctx, txs, 0, maxGas, true)
215+
if err != nil {
216+
b.Fatalf("FilterTxs failed: %v", err)
217+
}
218+
}
219+
})
220+
}
221+
}
222+
223+
// BenchmarkFilterTxs_WithSizeLimit benchmarks FilterTxs with byte size limit enforcement.
224+
func BenchmarkFilterTxs_WithSizeLimit(b *testing.B) {
225+
client := createBenchClient(b)
226+
ctx := context.Background()
227+
228+
txCounts := []int{100, 1000}
229+
// Size limit that allows roughly half the transactions (~110 bytes per tx)
230+
maxBytes := uint64(110 * 500)
231+
232+
for _, count := range txCounts {
233+
b.Run(benchName(count)+"_noForced", func(b *testing.B) {
234+
txs := generateTransactionBatch(b, count, 21000)
235+
236+
b.ResetTimer()
237+
b.ReportAllocs()
238+
239+
for b.Loop() {
240+
// Without forced transactions - UnmarshalBinary skipped
241+
_, err := client.FilterTxs(ctx, txs, maxBytes, 0, false)
242+
if err != nil {
243+
b.Fatalf("FilterTxs failed: %v", err)
244+
}
245+
}
246+
})
247+
248+
b.Run(benchName(count)+"_withForced", func(b *testing.B) {
249+
txs := generateTransactionBatch(b, count, 21000)
250+
251+
b.ResetTimer()
252+
b.ReportAllocs()
253+
254+
for b.Loop() {
255+
// With forced transactions - UnmarshalBinary called
256+
_, err := client.FilterTxs(ctx, txs, maxBytes, 0, true)
257+
if err != nil {
258+
b.Fatalf("FilterTxs failed: %v", err)
259+
}
260+
}
261+
})
262+
}
263+
}
264+
265+
// BenchmarkFilterTxs_CompareUnmarshalOverhead directly compares the overhead of UnmarshalBinary.
266+
// Runs the same transaction set with and without the UnmarshalBinary path.
267+
func BenchmarkFilterTxs_CompareUnmarshalOverhead(b *testing.B) {
268+
client := createBenchClient(b)
269+
ctx := context.Background()
270+
271+
count := 1000
272+
txs := generateTransactionBatch(b, count, 21000)
273+
274+
b.Run("without_unmarshal", func(b *testing.B) {
275+
b.ResetTimer()
276+
b.ReportAllocs()
277+
278+
for b.Loop() {
279+
_, err := client.FilterTxs(ctx, txs, 0, 0, false)
280+
if err != nil {
281+
b.Fatalf("FilterTxs failed: %v", err)
282+
}
283+
}
284+
})
285+
286+
b.Run("with_unmarshal", func(b *testing.B) {
287+
b.ResetTimer()
288+
b.ReportAllocs()
289+
290+
for b.Loop() {
291+
_, err := client.FilterTxs(ctx, txs, 0, 0, true)
292+
if err != nil {
293+
b.Fatalf("FilterTxs failed: %v", err)
294+
}
295+
}
296+
})
297+
}

0 commit comments

Comments
 (0)