Skip to content

Commit 91251d3

Browse files
authored
Merge branch 'main' into gprusak-fix
2 parents 22d4f7e + b01060b commit 91251d3

File tree

19 files changed

+1955
-117
lines changed

19 files changed

+1955
-117
lines changed

Makefile

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ ldflags := $(strip $(ldflags))
6767
# BUILD_FLAGS := -tags "$(build_tags)" -ldflags '$(ldflags)' -race
6868
BUILD_FLAGS := -tags "$(build_tags)" -ldflags '$(ldflags)'
6969
BUILD_FLAGS_MOCK_BALANCES := -tags "$(build_tags) mock_balances" -ldflags '$(ldflags)'
70+
BUILD_FLAGS_BENCHMARK := -tags "$(build_tags) benchmark mock_balances" -ldflags '$(ldflags)'
7071

7172
#### Command List ####
7273

@@ -78,6 +79,9 @@ install: go.sum
7879
install-mock-balances: go.sum
7980
go install $(BUILD_FLAGS_MOCK_BALANCES) ./cmd/seid
8081

82+
install-bench: go.sum
83+
go install $(BUILD_FLAGS_BENCHMARK) ./cmd/seid
84+
8185
install-with-race-detector: go.sum
8286
go install -race $(BUILD_FLAGS) ./cmd/seid
8387

app/app.go

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package app
22

33
import (
44
"bytes"
5+
"context"
56
"crypto/sha256"
67
"encoding/json"
78
"fmt"
@@ -114,6 +115,7 @@ import (
114115
"github.com/sei-protocol/sei-chain/x/evm"
115116
evmante "github.com/sei-protocol/sei-chain/x/evm/ante"
116117
"github.com/sei-protocol/sei-chain/x/evm/blocktest"
118+
evmconfig "github.com/sei-protocol/sei-chain/x/evm/config"
117119
evmkeeper "github.com/sei-protocol/sei-chain/x/evm/keeper"
118120
"github.com/sei-protocol/sei-chain/x/evm/querier"
119121
"github.com/sei-protocol/sei-chain/x/evm/replay"
@@ -380,6 +382,9 @@ type App struct {
380382
wsServerStartSignalSent bool
381383

382384
txPrioritizer sdk.TxPrioritizer
385+
386+
benchmarkProposalCh <-chan *abci.ResponsePrepareProposal
387+
benchmarkLogger *benchmarkLogger
383388
}
384389

385390
type AppOption func(*App)
@@ -854,7 +859,16 @@ func New(
854859

855860
app.SetAnteHandler(anteHandler)
856861
app.SetMidBlocker(app.MidBlocker)
857-
app.SetPrepareProposalHandler(app.PrepareProposalHandler)
862+
863+
// benchmarkEnabled is enabled via build flag (make install-bench)
864+
if benchmarkEnabled {
865+
evmChainID := evmconfig.GetEVMChainID(app.ChainID).Int64()
866+
app.InitGenerator(context.Background(), app.ChainID, evmChainID, logger)
867+
app.SetPrepareProposalHandler(app.PrepareProposalGeneratorHandler)
868+
} else {
869+
app.SetPrepareProposalHandler(app.PrepareProposalHandler)
870+
}
871+
858872
app.SetProcessProposalHandler(app.ProcessProposalHandler)
859873
app.SetFinalizeBlocker(app.FinalizeBlocker)
860874
app.SetInplaceTestnetInitializer(app.inplacetestnetInitializer)
@@ -900,6 +914,7 @@ func New(
900914

901915
app.txPrioritizer = NewSeiTxPrioritizer(logger, &app.EvmKeeper, &app.UpgradeKeeper, &app.ParamsKeeper).GetTxPriorityHint
902916
app.SetTxPrioritizer(app.txPrioritizer)
917+
903918
return app
904919
}
905920

app/benchmark.go

Lines changed: 278 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,278 @@
1+
package app
2+
3+
import (
4+
"context"
5+
"sync"
6+
"time"
7+
8+
"github.com/cosmos/cosmos-sdk/client"
9+
sdk "github.com/cosmos/cosmos-sdk/types"
10+
evmcfg "github.com/sei-protocol/sei-chain/x/evm/config"
11+
evmtypes "github.com/sei-protocol/sei-chain/x/evm/types"
12+
"github.com/sei-protocol/sei-chain/x/evm/types/ethtx"
13+
"github.com/sei-protocol/sei-load/config"
14+
"github.com/sei-protocol/sei-load/generator"
15+
"github.com/sei-protocol/sei-load/generator/scenarios"
16+
abci "github.com/tendermint/tendermint/abci/types"
17+
"github.com/tendermint/tendermint/libs/log"
18+
)
19+
20+
type benchmarkLogger struct {
21+
mx sync.Mutex
22+
txCount int64 // Total transactions processed
23+
blockCount int64 // Number of times Increment was called (number of blocks)
24+
latestHeight int64 // Highest height seen in the window
25+
maxBlockTime time.Duration // Maximum time difference between consecutive blocks
26+
totalBlockTime time.Duration // Sum of all block time differences in the window
27+
blockTimeCount int64 // Number of block time differences calculated
28+
peakTps float64 // Highest TPS seen across entire execution (persists across flushes)
29+
prevBlockTime time.Time // Previous block time for calculating differences
30+
lastFlushTime time.Time // When we last flushed (for TPS calculation)
31+
logger log.Logger
32+
}
33+
34+
func (l *benchmarkLogger) Increment(count int64, blocktime time.Time, height int64) {
35+
l.mx.Lock()
36+
defer l.mx.Unlock()
37+
38+
// Initialize lastFlushTime on first increment (when blocks actually start processing)
39+
if l.lastFlushTime.IsZero() {
40+
l.lastFlushTime = time.Now()
41+
}
42+
43+
l.txCount += count
44+
l.blockCount++
45+
if height > l.latestHeight {
46+
l.latestHeight = height
47+
}
48+
49+
// Calculate time difference between consecutive blocks
50+
if !l.prevBlockTime.IsZero() {
51+
blockTimeDiff := blocktime.Sub(l.prevBlockTime)
52+
if blockTimeDiff > l.maxBlockTime {
53+
l.maxBlockTime = blockTimeDiff
54+
}
55+
l.totalBlockTime += blockTimeDiff
56+
l.blockTimeCount++
57+
}
58+
l.prevBlockTime = blocktime
59+
}
60+
61+
// calculateTPS computes transactions per second based on transaction count and duration
62+
func calculateTPS(txCount int64, duration time.Duration) float64 {
63+
if duration <= 0 {
64+
return 0
65+
}
66+
return float64(txCount) / duration.Seconds()
67+
}
68+
69+
// calculateAvgBlockTime computes the average block time from total block time and count
70+
func calculateAvgBlockTime(totalBlockTime time.Duration, blockTimeCount int64) int64 {
71+
if blockTimeCount <= 0 {
72+
return 0
73+
}
74+
avgBlockTime := totalBlockTime / time.Duration(blockTimeCount)
75+
return avgBlockTime.Milliseconds()
76+
}
77+
78+
// flushStats holds the statistics for a flush window
79+
type flushStats struct {
80+
txCount int64
81+
blockCount int64
82+
latestHeight int64
83+
maxBlockTimeMs int64
84+
avgBlockTimeMs int64
85+
tps float64
86+
peakTps float64
87+
}
88+
89+
// getAndResetStats atomically reads current stats and resets counters for next window
90+
func (l *benchmarkLogger) getAndResetStats(now time.Time) (flushStats, time.Time) {
91+
l.mx.Lock()
92+
defer l.mx.Unlock()
93+
94+
stats := flushStats{
95+
txCount: l.txCount,
96+
blockCount: l.blockCount,
97+
latestHeight: l.latestHeight,
98+
maxBlockTimeMs: l.maxBlockTime.Milliseconds(),
99+
}
100+
101+
prevTime := l.lastFlushTime
102+
totalBlockTime := l.totalBlockTime
103+
blockTimeCount := l.blockTimeCount
104+
105+
// Reset counters for next window (but keep prevBlockTime and peakTps for continuity)
106+
l.txCount = 0
107+
l.blockCount = 0
108+
l.latestHeight = 0
109+
l.maxBlockTime = 0
110+
l.totalBlockTime = 0
111+
l.blockTimeCount = 0
112+
l.lastFlushTime = now
113+
114+
// Calculate TPS
115+
duration := now.Sub(prevTime)
116+
if duration > 0 && !prevTime.IsZero() {
117+
stats.tps = calculateTPS(stats.txCount, duration)
118+
}
119+
120+
// Calculate average block time
121+
stats.avgBlockTimeMs = calculateAvgBlockTime(totalBlockTime, blockTimeCount)
122+
123+
// Update peak TPS if current TPS is higher
124+
if stats.tps > l.peakTps {
125+
l.peakTps = stats.tps
126+
}
127+
stats.peakTps = l.peakTps
128+
129+
return stats, prevTime
130+
}
131+
132+
func (l *benchmarkLogger) FlushLog() {
133+
now := time.Now()
134+
stats, _ := l.getAndResetStats(now)
135+
136+
l.logger.Info("benchmark",
137+
"txs", stats.txCount,
138+
"blocks", stats.blockCount,
139+
"height", stats.latestHeight,
140+
"blockTimeMax", stats.maxBlockTimeMs,
141+
"blockTimeAvg", stats.avgBlockTimeMs,
142+
"tps", stats.tps,
143+
"peakTps", stats.peakTps,
144+
)
145+
}
146+
147+
func (l *benchmarkLogger) Start(ctx context.Context) {
148+
ticker := time.NewTicker(5 * time.Second)
149+
defer ticker.Stop()
150+
for {
151+
select {
152+
case <-ctx.Done():
153+
return
154+
case <-ticker.C:
155+
l.FlushLog()
156+
}
157+
}
158+
}
159+
160+
func NewGeneratorCh(ctx context.Context, txConfig client.TxConfig, chainID string, evmChainID int64, logger log.Logger) <-chan *abci.ResponsePrepareProposal {
161+
gen, err := generator.NewConfigBasedGenerator(&config.LoadConfig{
162+
ChainID: evmChainID,
163+
SeiChainID: chainID,
164+
Accounts: &config.AccountConfig{Accounts: 5000},
165+
Scenarios: []config.Scenario{{
166+
Name: scenarios.EVMTransfer,
167+
Weight: 1,
168+
}},
169+
})
170+
if err != nil {
171+
panic("failed to initialize generator: " + err.Error())
172+
}
173+
ch := make(chan *abci.ResponsePrepareProposal, 100)
174+
go func() {
175+
defer close(ch)
176+
var height int64
177+
for {
178+
// bail on ctx err
179+
if ctx.Err() != nil {
180+
return
181+
}
182+
// generate txs like: txs := gen.GenerateN(1000)
183+
loadTxs := gen.GenerateN(1000)
184+
if len(loadTxs) == 0 {
185+
continue
186+
}
187+
188+
// Convert LoadTx to Cosmos SDK transaction bytes
189+
txRecords := make([]*abci.TxRecord, 0, len(loadTxs))
190+
for _, loadTx := range loadTxs {
191+
if loadTx.EthTx == nil {
192+
continue
193+
}
194+
195+
// Convert Ethereum transaction to Cosmos SDK format
196+
txData, err := ethtx.NewTxDataFromTx(loadTx.EthTx)
197+
if err != nil {
198+
logger.Error("failed to convert eth tx to tx data", "error", err)
199+
panic(err)
200+
}
201+
202+
msg, err := evmtypes.NewMsgEVMTransaction(txData)
203+
if err != nil {
204+
logger.Error("failed to create msg evm transaction", "error", err)
205+
panic(err)
206+
}
207+
208+
gasUsedEstimate := loadTx.EthTx.Gas() // Use gas limit from transaction
209+
210+
txBuilder := txConfig.NewTxBuilder()
211+
if err = txBuilder.SetMsgs(msg); err != nil {
212+
logger.Error("failed to set msgs", "error", err)
213+
panic(err)
214+
}
215+
txBuilder.SetGasEstimate(gasUsedEstimate)
216+
217+
txbz, encodeErr := txConfig.TxEncoder()(txBuilder.GetTx())
218+
if encodeErr != nil {
219+
logger.Error("failed to encode tx", "error", encodeErr)
220+
panic(encodeErr)
221+
}
222+
223+
txRecords = append(txRecords, &abci.TxRecord{
224+
Action: abci.TxRecord_UNMODIFIED,
225+
Tx: txbz,
226+
})
227+
}
228+
229+
if len(txRecords) == 0 {
230+
continue
231+
}
232+
233+
proposal := &abci.ResponsePrepareProposal{
234+
TxRecords: txRecords,
235+
}
236+
237+
height++
238+
select {
239+
case ch <- proposal:
240+
case <-ctx.Done():
241+
return
242+
}
243+
}
244+
}()
245+
return ch
246+
}
247+
248+
// InitGenerator initializes the benchmark generator with default config
249+
func (app *App) InitGenerator(ctx context.Context, chainID string, evmChainID int64, logger log.Logger) {
250+
// defensive logic just to prevent this from initializing
251+
if evmcfg.IsLiveEVMChainID(evmChainID) {
252+
panic("benchmark not allowed on live chains")
253+
}
254+
logger.Info("Initializing benchmark mode generator", "mode", "benchmark")
255+
app.benchmarkLogger = &benchmarkLogger{
256+
logger: logger,
257+
}
258+
go app.benchmarkLogger.Start(ctx)
259+
app.benchmarkProposalCh = NewGeneratorCh(ctx, app.encodingConfig.TxConfig, chainID, evmChainID, logger)
260+
logger.Info("Benchmark generator initialized and started", "config", "default EVM Transfers")
261+
}
262+
263+
func (app *App) PrepareProposalGeneratorHandler(_ sdk.Context, req *abci.RequestPrepareProposal) (*abci.ResponsePrepareProposal, error) {
264+
select {
265+
case proposal, ok := <-app.benchmarkProposalCh:
266+
if proposal == nil || !ok {
267+
return &abci.ResponsePrepareProposal{
268+
TxRecords: []*abci.TxRecord{},
269+
}, nil
270+
}
271+
app.benchmarkLogger.Increment(int64(len(proposal.TxRecords)), req.Time, req.Height)
272+
return proposal, nil
273+
default:
274+
return &abci.ResponsePrepareProposal{
275+
TxRecords: []*abci.TxRecord{},
276+
}, nil
277+
}
278+
}

app/benchmark_buildtag.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
//go:build benchmark
2+
3+
package app
4+
5+
// benchmarkEnabled is set to true when built with benchmark build tag
6+
const benchmarkEnabled = true

app/benchmark_buildtag_default.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
//go:build !benchmark
2+
3+
package app
4+
5+
// benchmarkEnabled is set to false when not built with benchmark build tag
6+
const benchmarkEnabled = false

0 commit comments

Comments
 (0)