-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathgenerator.go
More file actions
223 lines (188 loc) · 6.47 KB
/
generator.go
File metadata and controls
223 lines (188 loc) · 6.47 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
package generator
import (
"errors"
"fmt"
"log"
"sync"
"github.com/ethereum/go-ethereum/common"
"github.com/sei-protocol/sei-load/config"
"github.com/sei-protocol/sei-load/generator/scenarios"
"github.com/sei-protocol/sei-load/types"
)
// Generator interface defines the contract for transaction generators
type Generator interface {
Generate() (*types.LoadTx, bool) // Returns transaction and true if more available, nil/false when done
GenerateN(n int) []*types.LoadTx
GetAccountPools() []types.AccountPool
}
// scenarioInstance represents a scenario instance with its configuration
type scenarioInstance struct {
Name string
Weight int
Scenario scenarios.TxGenerator
Accounts types.AccountPool
Deployed bool
}
// configBasedGenerator manages scenario creation and deployment from config
type configBasedGenerator struct {
config *config.LoadConfig
instances []*scenarioInstance
deployer *types.Account
sharedAccounts types.AccountPool // Shared account pool when using top-level config
accountPools []types.AccountPool // All account pools (shared + scenario-specific)
mu sync.RWMutex
}
// CreateScenarios creates scenario instances based on the configuration
// Each scenario entry in config creates a separate instance, even if same name
func (g *configBasedGenerator) createScenarios() error {
g.mu.Lock()
defer g.mu.Unlock()
// Create shared account pool if top-level account config exists
if g.config.Accounts != nil {
accounts := types.GenerateAccounts(g.config.Accounts.Accounts)
g.sharedAccounts = types.NewAccountPool(&types.AccountConfig{
Accounts: accounts,
NewAccountRate: g.config.Accounts.NewAccountRate,
})
g.accountPools = append(g.accountPools, g.sharedAccounts)
}
for i, scenarioCfg := range g.config.Scenarios {
// Create scenario instance using factory
scenario := scenarios.CreateScenario(scenarioCfg)
// Determine account pool to use
var accountPool types.AccountPool
if scenarioCfg.Accounts != nil {
// Scenario defines its own account settings - create separate pool
accountCount := scenarioCfg.Accounts.Accounts
newAccountRate := scenarioCfg.Accounts.NewAccountRate
accounts := types.GenerateAccounts(accountCount)
accountPool = types.NewAccountPool(&types.AccountConfig{
Accounts: accounts,
NewAccountRate: newAccountRate,
})
g.accountPools = append(g.accountPools, accountPool)
} else if g.sharedAccounts != nil {
// Use shared account pool from top-level config
accountPool = g.sharedAccounts
} else {
return errors.New("no accounts config defined")
}
// Count how many times this scenario name appears in the config
nameCount := 0
nameIndex := 0
for j, otherScenario := range g.config.Scenarios {
if otherScenario.Name == scenarioCfg.Name {
if j == i {
nameIndex = nameCount
}
nameCount++
}
}
name := scenarioCfg.Name
if nameCount > 1 {
name = fmt.Sprintf("%s_%d", name, nameIndex)
}
// Create scenario instance
instance := &scenarioInstance{
Name: name,
Weight: scenarioCfg.Weight,
Scenario: scenario,
Accounts: accountPool,
Deployed: false,
}
g.instances = append(g.instances, instance)
}
return nil
}
// mockDeployAll deploys all scenario instances that require deployment (for unit tests).
func (g *configBasedGenerator) mockDeployAll() error {
for _, instance := range g.instances {
addr := types.GenerateAccounts(1)[0].Address
if err := instance.Scenario.Attach(g.config, addr); err != nil {
return err
}
instance.Deployed = true
}
return nil
}
// DeployAll deploys all scenario instances that require deployment
func (g *configBasedGenerator) deployAll() error {
if g.config.MockDeploy {
return g.mockDeployAll()
}
g.mu.Lock()
defer g.mu.Unlock()
// Deploy sequentially to ensure proper nonce management
for _, instance := range g.instances {
// Deploy the scenario
log.Printf("Deploying scenario %s", instance.Name)
address := instance.Scenario.Deploy(g.config, g.deployer)
instance.Deployed = true
if address.Cmp(common.Address{}) != 0 {
log.Printf("🚀 Deployed %s at address: %s\n", instance.Name, address.Hex())
}
}
return nil
}
// createWeightedGenerator creates a weighted scenarioGenerator from deployed scenarios
func (g *configBasedGenerator) createWeightedGenerator() (Generator, error) {
g.mu.RLock()
defer g.mu.RUnlock()
if len(g.instances) == 0 {
return nil, fmt.Errorf("no scenario instances created")
}
// Check that all scenarios are deployed
for _, instance := range g.instances {
if !instance.Deployed {
return nil, fmt.Errorf("scenario %s is not deployed", instance.Name)
}
}
// Create weighted configurations
var weightedConfigs []*WeightedCfg
for _, instance := range g.instances {
if instance.Weight == 0 {
log.Printf("Skipping scenario %s with weight 0", instance.Name)
continue
}
// Create a scenarioGenerator for this scenario instance
gen := NewScenarioGenerator(instance.Accounts, instance.Scenario)
// Add to weighted config with the specified weight
weightedConfigs = append(weightedConfigs, WeightedConfig(instance.Weight, gen))
}
if len(weightedConfigs) == 0 {
return nil, fmt.Errorf("no scenario instances created (define some scenarios)")
}
// Create and return the weighted scenarioGenerator
return NewWeightedGenerator(weightedConfigs...), nil
}
// GetAccountPools returns all account pools managed by this generator
func (g *configBasedGenerator) GetAccountPools() []types.AccountPool {
g.mu.RLock()
defer g.mu.RUnlock()
// Return a copy of the slice to prevent external modification
pools := make([]types.AccountPool, len(g.accountPools))
copy(pools, g.accountPools)
return pools
}
// NewConfigBasedGenerator is a convenience method that combines all steps
func NewConfigBasedGenerator(cfg *config.LoadConfig) (Generator, error) {
generator := &configBasedGenerator{
config: cfg,
instances: make([]*scenarioInstance, 0),
deployer: types.GenerateAccounts(1)[0],
}
// Step 1: Create scenarios
if err := generator.createScenarios(); err != nil {
return nil, fmt.Errorf("failed to create scenarios: %w", err)
}
// Step 2: Deploy all scenarios
if err := generator.deployAll(); err != nil {
return nil, fmt.Errorf("failed to deploy scenarios: %w", err)
}
// Step 3: Create weighted scenarioGenerator
weightedGen, err := generator.createWeightedGenerator()
if err != nil {
return nil, fmt.Errorf("failed to create weighted scenarioGenerator: %w", err)
}
return weightedGen, nil
}