Skip to content

Commit ee280ba

Browse files
committed
save txs to file
1 parent 052ec99 commit ee280ba

5 files changed

Lines changed: 195 additions & 11 deletions

File tree

config/settings.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ type Settings struct {
2424
Prewarm bool `json:"prewarm,omitempty"`
2525
RampUp bool `json:"rampUp,omitempty"`
2626
ReportPath string `json:"reportPath,omitempty"`
27+
TxsDir string `json:"txsDir,omitempty"`
2728
}
2829

2930
// DefaultSettings returns the default configuration values
@@ -41,6 +42,7 @@ func DefaultSettings() Settings {
4142
Prewarm: false,
4243
RampUp: false,
4344
ReportPath: "",
45+
TxsDir: "",
4446
}
4547
}
4648

@@ -60,6 +62,7 @@ func InitializeViper(cmd *cobra.Command) error {
6062
"workers": "workers",
6163
"rampUp": "ramp-up",
6264
"reportPath": "report-path",
65+
"txsDir": "txs-dir",
6366
}
6467

6568
for viperKey, flagName := range flagBindings {
@@ -82,6 +85,7 @@ func InitializeViper(cmd *cobra.Command) error {
8285
viper.SetDefault("workers", defaults.Workers)
8386
viper.SetDefault("rampUp", defaults.RampUp)
8487
viper.SetDefault("reportPath", defaults.ReportPath)
88+
viper.SetDefault("txsDir", defaults.TxsDir)
8589
return nil
8690
}
8791

@@ -120,5 +124,6 @@ func ResolveSettings() Settings {
120124
Prewarm: viper.GetBool("prewarm"),
121125
RampUp: viper.GetBool("rampUp"),
122126
ReportPath: viper.GetString("reportPath"),
127+
TxsDir: viper.GetString("txsDir"),
123128
}
124129
}

main.go

Lines changed: 19 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ func init() {
6565
rootCmd.Flags().String("metricsListenAddr", "0.0.0.0:9090", "The ip:port on which to export prometheus metrics.")
6666
rootCmd.Flags().Bool("ramp-up", false, "Ramp up loadtest")
6767
rootCmd.Flags().String("report-path", "", "Path to save the report")
68+
rootCmd.Flags().String("txs-dir", "", "Path to save the transactions")
6869

6970
// Initialize Viper with proper error handling
7071
if err := config.InitializeViper(rootCmd); err != nil {
@@ -169,12 +170,6 @@ func runLoadTest(ctx context.Context, cmd *cobra.Command, args []string) error {
169170
sharedLimiter = rate.NewLimiter(rate.Inf, 1)
170171
}
171172

172-
// Create the sender from the config struct
173-
snd, err := sender.NewShardedSender(cfg, settings.BufferSize, settings.Workers, sharedLimiter)
174-
if err != nil {
175-
return fmt.Errorf("failed to create sender: %w", err)
176-
}
177-
178173
// Create and start block collector if endpoints are available
179174
var blockCollector *stats.BlockCollector
180175
if len(cfg.Endpoints) > 0 && settings.TrackBlocks {
@@ -207,6 +202,12 @@ func runLoadTest(ctx context.Context, cmd *cobra.Command, args []string) error {
207202
})
208203
}
209204

205+
// Create the sender from the config struct
206+
snd, err := sender.NewShardedSender(cfg, settings.BufferSize, settings.Workers, sharedLimiter)
207+
if err != nil {
208+
return fmt.Errorf("failed to create sender: %w", err)
209+
}
210+
210211
// Enable dry-run mode in sender if specified
211212
if settings.DryRun {
212213
snd.SetDryRun(true)
@@ -225,7 +226,13 @@ func runLoadTest(ctx context.Context, cmd *cobra.Command, args []string) error {
225226
snd.SetStatsCollector(collector, logger)
226227

227228
// Create dispatcher
228-
dispatcher := sender.NewDispatcher(gen, snd)
229+
var dispatcher *sender.Dispatcher
230+
if settings.TxsDir != "" {
231+
writer := sender.NewTxsWriter(10_000_000, settings.TxsDir)
232+
dispatcher = sender.NewDispatcher(gen, writer)
233+
} else {
234+
dispatcher = sender.NewDispatcher(gen, snd)
235+
}
229236

230237
// Set statistics collector for dispatcher
231238
dispatcher.SetStatsCollector(collector)
@@ -239,10 +246,11 @@ func runLoadTest(ctx context.Context, cmd *cobra.Command, args []string) error {
239246
log.Printf("📝 Prewarm mode: Accounts will be prewarmed")
240247
}
241248

242-
// Start the sender (starts all workers)
243-
s.SpawnBgNamed("sender", func() error { return snd.Run(ctx) })
244-
log.Printf("✅ Connected to %d endpoints", snd.GetNumShards())
245-
249+
if settings.TxsDir == "" {
250+
// Start the sender (starts all workers)
251+
s.SpawnBgNamed("sender", func() error { return snd.Run(ctx) })
252+
log.Printf("✅ Connected to %d endpoints", snd.GetNumShards())
253+
}
246254
// Perform prewarming if enabled (before starting logger to avoid logging prewarm transactions)
247255
if settings.Prewarm {
248256
if err := dispatcher.Prewarm(ctx); err != nil {

profiles/conflict.json

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
{
2+
"chainId": 713714,
3+
"seiChainId": "sei-chain",
4+
"endpoints": [
5+
"http://127.0.0.1:8545"
6+
],
7+
"accounts": {
8+
"count": 5000,
9+
"newAccountRate": 0.0
10+
},
11+
"scenarios": [
12+
{
13+
"name": "ERC20Conflict",
14+
"weight": 1
15+
}
16+
],
17+
"settings": {
18+
"workers": 1,
19+
"tps": 0,
20+
"statsInterval": "5s",
21+
"bufferSize": 1000,
22+
"dryRun": false,
23+
"debug": false,
24+
"trackReceipts": false,
25+
"trackBlocks": false,
26+
"trackUserLatency": false,
27+
"prewarm": false,
28+
"rampUp": false
29+
}
30+
}

sender/writer.go

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
package sender
2+
3+
import (
4+
"context"
5+
"encoding/json"
6+
"fmt"
7+
"log"
8+
"os"
9+
"path/filepath"
10+
11+
"github.com/sei-protocol/sei-load/types"
12+
)
13+
14+
// implements `Send`
15+
16+
type TxsWriter struct {
17+
gasPerBlock uint64
18+
nextHeight uint64
19+
txsDir string
20+
21+
bufferGas uint64
22+
txBuffer []*types.LoadTx
23+
}
24+
25+
func NewTxsWriter(gasPerBlock uint64, txsDir string) *TxsWriter {
26+
// what height to start at?
27+
return &TxsWriter{
28+
gasPerBlock: gasPerBlock,
29+
nextHeight: 1,
30+
txsDir: txsDir,
31+
32+
bufferGas: 0,
33+
txBuffer: make([]*types.LoadTx, 0),
34+
}
35+
}
36+
37+
// Send writes the transaction to the writer
38+
func (w *TxsWriter) Send(ctx context.Context, tx *types.LoadTx) error {
39+
// if bwe would exceed gasPerBlock, flush
40+
if w.bufferGas+tx.EthTx.Gas() > w.gasPerBlock {
41+
if err := w.Flush(); err != nil {
42+
return err
43+
}
44+
}
45+
46+
// add to buffer
47+
w.txBuffer = append(w.txBuffer, tx)
48+
w.bufferGas += tx.EthTx.Gas()
49+
return nil
50+
}
51+
52+
type TxWriteData struct {
53+
TxPayloads [][]byte `json:"tx_payloads"`
54+
}
55+
56+
func (w *TxsWriter) Flush() error {
57+
defer func() {
58+
// clear buffer and reset bufferGas and increment nextHeight
59+
w.txBuffer = make([]*types.LoadTx, 0)
60+
w.bufferGas = 0
61+
w.nextHeight++
62+
}()
63+
// write to dir `~/load_txs`
64+
// make dir if it doesn't exist
65+
os.MkdirAll(w.txsDir, 0755)
66+
txsFile := filepath.Join(w.txsDir, fmt.Sprintf("%d_txs.json", w.nextHeight))
67+
txData := TxWriteData{
68+
TxPayloads: make([][]byte, 0),
69+
}
70+
for _, tx := range w.txBuffer {
71+
txData.TxPayloads = append(txData.TxPayloads, tx.Payload)
72+
}
73+
74+
txDataJSON, err := json.Marshal(txData)
75+
if err != nil {
76+
return err
77+
}
78+
79+
if err := os.WriteFile(txsFile, txDataJSON, 0644); err != nil {
80+
return err
81+
}
82+
83+
log.Printf("Flushed %d transactions to %s", len(w.txBuffer), txsFile)
84+
85+
return nil
86+
}

sender/writer_test.go

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
package sender
2+
3+
import (
4+
"context"
5+
"testing"
6+
7+
"github.com/sei-protocol/sei-load/config"
8+
"github.com/sei-protocol/sei-load/generator"
9+
"github.com/sei-protocol/sei-load/generator/scenarios"
10+
"github.com/sei-protocol/sei-load/types"
11+
"github.com/stretchr/testify/require"
12+
)
13+
14+
func TestTxsWriter_Flush(t *testing.T) {
15+
// two evm transfer txs
16+
writer := NewTxsWriter(42000, "/tmp")
17+
18+
loadConfig := &config.LoadConfig{
19+
ChainID: 7777,
20+
}
21+
22+
sharedAccounts := types.NewAccountPool(&types.AccountConfig{
23+
Accounts: types.GenerateAccounts(10),
24+
NewAccountRate: 0.0,
25+
})
26+
27+
evmScenario := scenarios.CreateScenario(config.Scenario{
28+
Name: "EVMTransfer",
29+
Weight: 1,
30+
})
31+
evmScenario.Deploy(loadConfig, sharedAccounts.NextAccount())
32+
33+
generator := generator.NewScenarioGenerator(sharedAccounts, evmScenario)
34+
35+
txs := generator.GenerateN(3)
36+
37+
writer.Send(context.Background(), txs[0])
38+
require.Equal(t, uint64(1), writer.nextHeight)
39+
require.Equal(t, uint64(21000), writer.bufferGas)
40+
require.Len(t, writer.txBuffer, 1)
41+
require.Equal(t, txs[0], writer.txBuffer[0])
42+
43+
writer.Send(context.Background(), txs[1])
44+
require.Equal(t, uint64(1), writer.nextHeight)
45+
require.Equal(t, uint64(42000), writer.bufferGas)
46+
require.Len(t, writer.txBuffer, 2)
47+
require.Equal(t, txs[1], writer.txBuffer[1])
48+
49+
writer.Send(context.Background(), txs[2])
50+
// now should be flushed and have the new tx
51+
require.Equal(t, uint64(2), writer.nextHeight)
52+
require.Equal(t, uint64(21000), writer.bufferGas)
53+
require.Len(t, writer.txBuffer, 1)
54+
55+
}

0 commit comments

Comments
 (0)