Skip to content

Commit 7150135

Browse files
committed
Introduce phase1 benchmark
1 parent b3b0881 commit 7150135

File tree

2 files changed

+138
-0
lines changed

2 files changed

+138
-0
lines changed

.github/workflows/benchmark.yml

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,3 +40,19 @@ jobs:
4040
alert-threshold: '150%'
4141
fail-on-alert: true
4242
comment-on-alert: true
43+
44+
- name: Run Block Executor benchmarks
45+
run: |
46+
go test -bench=BenchmarkProduceBlock -benchmem -run='^$' \
47+
./block/internal/executing/... > block_executor_output.txt
48+
- name: Store Block Executor benchmark result
49+
uses: benchmark-action/github-action-benchmark@4bdcce38c94cec68da58d012ac24b7b1155efe8b # v1.20.7
50+
with:
51+
name: Block Executor Benchmark
52+
tool: 'go'
53+
output-file-path: block_executor_output.txt
54+
auto-push: true
55+
github-token: ${{ secrets.GITHUB_TOKEN }}
56+
alert-threshold: '150%'
57+
fail-on-alert: true
58+
comment-on-alert: true
Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
package executing
2+
3+
import (
4+
"context"
5+
"crypto/rand"
6+
"testing"
7+
"time"
8+
9+
"github.com/ipfs/go-datastore"
10+
"github.com/ipfs/go-datastore/sync"
11+
"github.com/libp2p/go-libp2p/core/crypto"
12+
"github.com/rs/zerolog"
13+
"github.com/stretchr/testify/mock"
14+
"github.com/stretchr/testify/require"
15+
16+
"github.com/evstack/ev-node/block/internal/cache"
17+
"github.com/evstack/ev-node/block/internal/common"
18+
coreseq "github.com/evstack/ev-node/core/sequencer"
19+
"github.com/evstack/ev-node/pkg/config"
20+
"github.com/evstack/ev-node/pkg/genesis"
21+
"github.com/evstack/ev-node/pkg/signer/noop"
22+
"github.com/evstack/ev-node/pkg/store"
23+
testmocks "github.com/evstack/ev-node/test/mocks"
24+
"github.com/evstack/ev-node/types"
25+
)
26+
27+
// newBenchExecutor creates a fully wired Executor with mocked external
28+
// dependencies, ready for benchmarking ProduceBlock.
29+
func newBenchExecutor(b *testing.B, txs [][]byte) *Executor {
30+
b.Helper()
31+
32+
ds := sync.MutexWrap(datastore.NewMapDatastore())
33+
memStore := store.New(ds)
34+
35+
cacheManager, err := cache.NewManager(config.DefaultConfig(), memStore, zerolog.Nop())
36+
require.NoError(b, err)
37+
38+
// Generate signer without depending on *testing.T
39+
priv, _, err := crypto.GenerateEd25519Key(rand.Reader)
40+
require.NoError(b, err)
41+
signerWrapper, err := noop.NewNoopSigner(priv)
42+
require.NoError(b, err)
43+
addr, err := signerWrapper.GetAddress()
44+
require.NoError(b, err)
45+
46+
cfg := config.DefaultConfig()
47+
cfg.Node.BlockTime = config.DurationWrapper{Duration: 10 * time.Millisecond}
48+
cfg.Node.MaxPendingHeadersAndData = 100000
49+
50+
gen := genesis.Genesis{
51+
ChainID: "bench-chain",
52+
InitialHeight: 1,
53+
StartTime: time.Now().Add(-time.Hour),
54+
ProposerAddress: addr,
55+
}
56+
57+
mockExec := testmocks.NewMockExecutor(b)
58+
mockSeq := testmocks.NewMockSequencer(b)
59+
hb := common.NewMockBroadcaster[*types.P2PSignedHeader](b)
60+
db := common.NewMockBroadcaster[*types.P2PData](b)
61+
62+
hb.EXPECT().WriteToStoreAndBroadcast(mock.Anything, mock.Anything).Return(nil)
63+
db.EXPECT().WriteToStoreAndBroadcast(mock.Anything, mock.Anything).Return(nil)
64+
65+
exec, err := NewExecutor(
66+
memStore, mockExec, mockSeq, signerWrapper,
67+
cacheManager, common.NopMetrics(), cfg, gen,
68+
hb, db, zerolog.Nop(), common.DefaultBlockOptions(),
69+
make(chan error, 1), nil,
70+
)
71+
require.NoError(b, err)
72+
73+
// One-time init expectations
74+
mockExec.EXPECT().InitChain(mock.Anything, mock.AnythingOfType("time.Time"), gen.InitialHeight, gen.ChainID).
75+
Return([]byte("init_root"), nil).Once()
76+
mockSeq.EXPECT().SetDAHeight(uint64(0)).Return().Once()
77+
78+
require.NoError(b, exec.initializeState())
79+
80+
exec.ctx, exec.cancel = context.WithCancel(b.Context())
81+
b.Cleanup(func() { exec.cancel() })
82+
83+
// Loop expectations (unlimited calls)
84+
mockSeq.EXPECT().GetNextBatch(mock.Anything, mock.AnythingOfType("sequencer.GetNextBatchRequest")).
85+
Return(&coreseq.GetNextBatchResponse{
86+
Batch: &coreseq.Batch{Transactions: txs},
87+
Timestamp: time.Now(),
88+
BatchData: txs,
89+
}, nil)
90+
mockExec.EXPECT().ExecuteTxs(mock.Anything, mock.Anything, mock.Anything, mock.AnythingOfType("time.Time"), mock.Anything).
91+
Return([]byte("new_root"), nil)
92+
mockSeq.EXPECT().GetDAHeight().Return(uint64(0))
93+
94+
return exec
95+
}
96+
97+
func BenchmarkProduceBlock(b *testing.B) {
98+
specs := map[string]struct {
99+
txs [][]byte
100+
}{
101+
"empty batch": {
102+
txs: nil,
103+
},
104+
"single tx": {
105+
txs: [][]byte{[]byte("tx1")},
106+
},
107+
}
108+
for name, spec := range specs {
109+
b.Run(name, func(b *testing.B) {
110+
exec := newBenchExecutor(b, spec.txs)
111+
ctx := b.Context()
112+
113+
b.ReportAllocs()
114+
b.ResetTimer()
115+
for i := 0; i < b.N; i++ {
116+
if err := exec.ProduceBlock(ctx); err != nil {
117+
b.Fatalf("ProduceBlock: %v", err)
118+
}
119+
}
120+
})
121+
}
122+
}

0 commit comments

Comments
 (0)