Skip to content

Commit eff0e15

Browse files
committed
Move datadir management to separate module
1 parent be41d20 commit eff0e15

File tree

3 files changed

+413
-258
lines changed

3 files changed

+413
-258
lines changed

runner/clients/reth/client.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ func (r *RethClient) Run(ctx context.Context, cfg *types.RuntimeConfig) error {
9090
args = append(args, "--authrpc.jwtsecret", r.options.JWTSecretPath)
9191
args = append(args, "--metrics", fmt.Sprintf("%d", r.metricsPort))
9292
args = append(args, "--engine.state-provider-metrics")
93-
args = append(args, "-vvv")
93+
args = append(args, "-vvvv")
9494

9595
args = append(args, cfg.Args...)
9696

runner/datadir/manager.go

Lines changed: 327 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,327 @@
1+
package datadir
2+
3+
import (
4+
"crypto/rand"
5+
"encoding/hex"
6+
"encoding/json"
7+
"fmt"
8+
"os"
9+
"path"
10+
11+
"github.com/base/base-bench/runner/benchmark"
12+
"github.com/base/base-bench/runner/config"
13+
"github.com/base/base-bench/runner/network/types"
14+
"github.com/ethereum/go-ethereum/core"
15+
"github.com/ethereum/go-ethereum/log"
16+
"github.com/pkg/errors"
17+
)
18+
19+
// Manager handles the creation and management of test data directories
20+
type Manager struct {
21+
// tracks persistent test directories for reuse_existing snapshots
22+
// key: nodeType, value: map["sequencer"|"validator"] -> TestDirConfig
23+
persistentTestDirs map[string]map[string]*TestDirConfig
24+
25+
snapshotManager benchmark.SnapshotManager
26+
workingDir string
27+
log log.Logger
28+
}
29+
30+
// TestDirConfig contains the configuration for a test directory
31+
type TestDirConfig struct {
32+
SequencerOptions *config.InternalClientOptions
33+
ValidatorOptions *config.InternalClientOptions
34+
}
35+
36+
// NewManager creates a new DataDirManager
37+
func NewManager(workingDir string, snapshotManager benchmark.SnapshotManager, log log.Logger) *Manager {
38+
return &Manager{
39+
persistentTestDirs: make(map[string]map[string]*TestDirConfig),
40+
snapshotManager: snapshotManager,
41+
workingDir: workingDir,
42+
log: log,
43+
}
44+
}
45+
46+
// fileExists checks if a file exists
47+
func (m *Manager) fileExists(path string) bool {
48+
_, err := os.Stat(path)
49+
return err == nil
50+
}
51+
52+
// SetupTestDirs sets up test directories for a benchmark run
53+
// For reuse_existing snapshots, it creates persistent directories that will be reused across tests
54+
// For other snapshot methods, directories will be created per-test in runTest
55+
func (m *Manager) SetupTestDirs(params types.RunParams, genesis *core.Genesis, snapshot *benchmark.SnapshotDefinition, clientOptions config.ClientOptions) (*TestDirConfig, error) {
56+
isReuseExisting := snapshot != nil && snapshot.GetSnapshotMethod() == benchmark.SnapshotMethodReuseExisting
57+
58+
if !isReuseExisting {
59+
return nil, nil
60+
}
61+
62+
// For reuse_existing, create persistent directories
63+
nodeType := params.NodeType
64+
65+
if _, exists := m.persistentTestDirs[nodeType]; !exists {
66+
m.persistentTestDirs[nodeType] = make(map[string]*TestDirConfig)
67+
}
68+
69+
// Check if we already have persistent directories for this node type
70+
if existingConfig, exists := m.persistentTestDirs[nodeType]["config"]; exists {
71+
m.log.Info("Reusing existing persistent test directories", "nodeType", nodeType)
72+
return existingConfig, nil
73+
}
74+
75+
// Create new persistent directories
76+
testName := fmt.Sprintf("persistent-%s", nodeType)
77+
sequencerTestDir := path.Join(m.workingDir, fmt.Sprintf("%s-sequencer", testName))
78+
validatorTestDir := path.Join(m.workingDir, fmt.Sprintf("%s-validator", testName))
79+
80+
m.log.Info("Creating persistent test directories for reuse_existing",
81+
"nodeType", nodeType,
82+
"sequencer", sequencerTestDir,
83+
"validator", validatorTestDir)
84+
85+
// Setup data directories
86+
sequencerOptions, validatorOptions, err := m.setupDataDirs(sequencerTestDir, validatorTestDir, params, genesis, snapshot, clientOptions)
87+
if err != nil {
88+
return nil, errors.Wrap(err, "failed to setup data dirs")
89+
}
90+
91+
testDirConfig := &TestDirConfig{
92+
SequencerOptions: sequencerOptions,
93+
ValidatorOptions: validatorOptions,
94+
}
95+
96+
m.persistentTestDirs[nodeType]["config"] = testDirConfig
97+
98+
return testDirConfig, nil
99+
}
100+
101+
// GetOrCreateTestDirs gets existing persistent directories or creates temporary ones
102+
func (m *Manager) GetOrCreateTestDirs(params types.RunParams, genesis *core.Genesis, snapshot *benchmark.SnapshotDefinition, clientOptions config.ClientOptions, testTimestamp int64) (*config.InternalClientOptions, *config.InternalClientOptions, bool, error) {
103+
isReuseExisting := snapshot != nil && snapshot.GetSnapshotMethod() == benchmark.SnapshotMethodReuseExisting
104+
105+
if isReuseExisting {
106+
// Return pre-configured persistent directories
107+
if config, exists := m.persistentTestDirs[params.NodeType]["config"]; exists {
108+
m.log.Info("Using persistent test directories", "nodeType", params.NodeType)
109+
return config.SequencerOptions, config.ValidatorOptions, false, nil // false = don't cleanup
110+
}
111+
return nil, nil, false, fmt.Errorf("persistent directories not setup for node type %s", params.NodeType)
112+
}
113+
114+
// For non-reuse_existing, create temporary directories
115+
testName := fmt.Sprintf("%d-%s-test", testTimestamp, params.NodeType)
116+
sequencerTestDir := path.Join(m.workingDir, fmt.Sprintf("%s-sequencer", testName))
117+
validatorTestDir := path.Join(m.workingDir, fmt.Sprintf("%s-validator", testName))
118+
119+
sequencerOptions, validatorOptions, err := m.setupDataDirs(sequencerTestDir, validatorTestDir, params, genesis, snapshot, clientOptions)
120+
if err != nil {
121+
return nil, nil, false, errors.Wrap(err, "failed to setup data dirs")
122+
}
123+
124+
return sequencerOptions, validatorOptions, true, nil // true = cleanup after test
125+
}
126+
127+
// setupDataDirs sets up the data directories for sequencer and validator
128+
func (m *Manager) setupDataDirs(sequencerTestDir string, validatorTestDir string, params types.RunParams, genesis *core.Genesis, snapshot *benchmark.SnapshotDefinition, clientOptions config.ClientOptions) (*config.InternalClientOptions, *config.InternalClientOptions, error) {
129+
var sequencerDataDirOverride, validatorDataDirOverride string
130+
131+
if snapshot != nil && snapshot.GetSnapshotMethod() == benchmark.SnapshotMethodReuseExisting {
132+
sequencerDataDirOverride = path.Join(sequencerTestDir, "data")
133+
validatorDataDirOverride = path.Join(validatorTestDir, "data")
134+
135+
// Check if this is the first run (directories don't exist yet)
136+
isFirstRun := !m.fileExists(sequencerDataDirOverride) && !m.fileExists(validatorDataDirOverride)
137+
138+
if isFirstRun {
139+
initialSnapshotPath := m.snapshotManager.GetInitialSnapshotPath(params.NodeType)
140+
if initialSnapshotPath != "" && m.fileExists(initialSnapshotPath) {
141+
m.log.Info("First run with reuse_existing: copying to validator, moving to sequencer",
142+
"initialSnapshot", initialSnapshotPath,
143+
"sequencerDataDir", sequencerDataDirOverride,
144+
"validatorDataDir", validatorDataDirOverride)
145+
146+
// First: copy from initial snapshot to validator directory
147+
err := m.snapshotManager.CopyFromInitialSnapshot(initialSnapshotPath, validatorDataDirOverride)
148+
if err != nil {
149+
return nil, nil, errors.Wrap(err, "failed to copy initial snapshot to validator directory")
150+
}
151+
m.log.Info("Copied initial snapshot to validator directory", "path", validatorDataDirOverride)
152+
153+
err = os.MkdirAll(sequencerTestDir, 0755)
154+
if err != nil {
155+
return nil, nil, errors.Wrap(err, "failed to create sequencer test directory")
156+
}
157+
158+
err = os.Rename(initialSnapshotPath, sequencerDataDirOverride)
159+
if err != nil {
160+
return nil, nil, errors.Wrap(err, "failed to move initial snapshot to sequencer directory")
161+
}
162+
m.log.Info("Moved initial snapshot to sequencer directory", "from", initialSnapshotPath, "to", sequencerDataDirOverride)
163+
}
164+
} else {
165+
m.log.Info("Reusing existing data directories from previous run",
166+
"sequencerDataDir", sequencerDataDirOverride,
167+
"validatorDataDir", validatorDataDirOverride)
168+
}
169+
}
170+
171+
sequencerOptions, err := m.setupInternalDirectories(sequencerTestDir, params, genesis, snapshot, "sequencer", sequencerDataDirOverride, clientOptions)
172+
if err != nil {
173+
return nil, nil, errors.Wrap(err, "failed to setup internal directories")
174+
}
175+
176+
validatorOptions, err := m.setupInternalDirectories(validatorTestDir, params, genesis, snapshot, "validator", validatorDataDirOverride, clientOptions)
177+
if err != nil {
178+
return nil, nil, errors.Wrap(err, "failed to setup internal directories")
179+
}
180+
181+
return sequencerOptions, validatorOptions, nil
182+
}
183+
184+
// setupInternalDirectories sets up the internal directory structure for a test
185+
func (m *Manager) setupInternalDirectories(testDir string, params types.RunParams, genesis *core.Genesis, snapshot *benchmark.SnapshotDefinition, role string, dataDirOverride string, clientOptions config.ClientOptions) (*config.InternalClientOptions, error) {
186+
err := os.MkdirAll(testDir, 0755)
187+
if err != nil {
188+
return nil, errors.Wrap(err, "failed to create test directory")
189+
}
190+
191+
metricsPath := path.Join(testDir, "metrics")
192+
// Use MkdirAll to avoid error if directory already exists
193+
err = os.MkdirAll(metricsPath, 0755)
194+
if err != nil {
195+
return nil, errors.Wrap(err, "failed to create metrics directory")
196+
}
197+
198+
// write chain config to testDir/chain.json
199+
chainCfgPath := path.Join(testDir, "chain.json")
200+
// Only create chain config if it doesn't exist (for reuse_existing)
201+
if !m.fileExists(chainCfgPath) {
202+
chainCfgFile, err := os.OpenFile(chainCfgPath, os.O_WRONLY|os.O_CREATE, 0644)
203+
if err != nil {
204+
return nil, errors.Wrap(err, "failed to open chain config file")
205+
}
206+
207+
err = json.NewEncoder(chainCfgFile).Encode(genesis)
208+
if err != nil {
209+
return nil, errors.Wrap(err, "failed to write chain config")
210+
}
211+
if err := chainCfgFile.Close(); err != nil {
212+
return nil, errors.Wrap(err, "failed to close chain config file")
213+
}
214+
}
215+
216+
var dataDirPath string
217+
var isSnapshot bool
218+
219+
// If dataDirOverride is provided, use it (already set up by caller)
220+
if dataDirOverride != "" {
221+
dataDirPath = dataDirOverride
222+
isSnapshot = true // dataDirOverride is only set when using snapshots
223+
m.log.Info("Using pre-configured datadir", "path", dataDirPath, "role", role)
224+
} else {
225+
isSnapshot = snapshot != nil && snapshot.Command != ""
226+
if isSnapshot {
227+
dataDirPath = path.Join(testDir, "data")
228+
229+
initialSnapshotPath := m.snapshotManager.GetInitialSnapshotPath(params.NodeType)
230+
231+
if initialSnapshotPath != "" && m.fileExists(initialSnapshotPath) {
232+
snapshotMethod := snapshot.GetSnapshotMethod()
233+
234+
switch snapshotMethod {
235+
case benchmark.SnapshotMethodReuseExisting:
236+
dataDirPath = initialSnapshotPath
237+
m.log.Info("Reusing existing snapshot", "snapshotPath", initialSnapshotPath, "method", snapshotMethod)
238+
case benchmark.SnapshotMethodHeadRollback:
239+
// For head_rollback, copy the snapshot but mark it for rollback later
240+
err := m.snapshotManager.CopyFromInitialSnapshot(initialSnapshotPath, dataDirPath)
241+
if err != nil {
242+
return nil, errors.Wrap(err, "failed to copy from initial snapshot for head rollback")
243+
}
244+
m.log.Info("Copied from initial snapshot for head rollback", "initialSnapshotPath", initialSnapshotPath, "dataDirPath", dataDirPath, "method", snapshotMethod)
245+
default:
246+
// Default chain_copy behavior
247+
err := m.snapshotManager.CopyFromInitialSnapshot(initialSnapshotPath, dataDirPath)
248+
if err != nil {
249+
return nil, errors.Wrap(err, "failed to copy from initial snapshot")
250+
}
251+
m.log.Info("Copied from initial snapshot", "initialSnapshotPath", initialSnapshotPath, "dataDirPath", dataDirPath)
252+
}
253+
} else {
254+
// Fallback to direct snapshot creation
255+
if initialSnapshotPath != "" {
256+
m.log.Warn("Initial snapshot path registered but doesn't exist, falling back to direct snapshot creation",
257+
"path", initialSnapshotPath, "nodeType", params.NodeType)
258+
}
259+
snapshotDir, err := m.snapshotManager.EnsureSnapshot(*snapshot, params.NodeType, role)
260+
if err != nil {
261+
return nil, errors.Wrap(err, "failed to ensure snapshot")
262+
}
263+
dataDirPath = snapshotDir
264+
}
265+
} else {
266+
// if no snapshot, just create a new datadir
267+
dataDirPath = path.Join(testDir, "data")
268+
err = os.Mkdir(dataDirPath, 0755)
269+
if err != nil {
270+
return nil, errors.Wrap(err, "failed to create data directory")
271+
}
272+
}
273+
}
274+
275+
jwtSecretPath := path.Join(testDir, "jwt_secret")
276+
var jwtSecretStr string
277+
278+
// Check if JWT secret already exists (for reuse_existing)
279+
if m.fileExists(jwtSecretPath) {
280+
jwtSecretBytes, err := os.ReadFile(jwtSecretPath)
281+
if err != nil {
282+
return nil, errors.Wrap(err, "failed to read existing jwt secret")
283+
}
284+
jwtSecretStr = string(jwtSecretBytes)
285+
m.log.Info("Reusing existing JWT secret", "path", jwtSecretPath, "role", role)
286+
} else {
287+
// Generate new JWT secret
288+
var jwtSecret [32]byte
289+
_, err = rand.Read(jwtSecret[:])
290+
if err != nil {
291+
return nil, errors.Wrap(err, "failed to generate jwt secret")
292+
}
293+
294+
jwtSecretFile, err := os.OpenFile(jwtSecretPath, os.O_WRONLY|os.O_CREATE, 0644)
295+
if err != nil {
296+
return nil, errors.Wrap(err, "failed to open jwt secret file")
297+
}
298+
299+
jwtSecretStr = hex.EncodeToString(jwtSecret[:])
300+
_, err = jwtSecretFile.Write([]byte(jwtSecretStr))
301+
if err != nil {
302+
return nil, errors.Wrap(err, "failed to write jwt secret")
303+
}
304+
305+
if err = jwtSecretFile.Close(); err != nil {
306+
return nil, errors.Wrap(err, "failed to close jwt secret file")
307+
}
308+
m.log.Info("Generated new JWT secret", "path", jwtSecretPath, "role", role)
309+
}
310+
311+
options := clientOptions
312+
options = params.ClientOptions(options)
313+
314+
options.SkipInit = isSnapshot
315+
316+
internalOptions := &config.InternalClientOptions{
317+
ClientOptions: options,
318+
JWTSecretPath: jwtSecretPath,
319+
MetricsPath: metricsPath,
320+
JWTSecret: jwtSecretStr,
321+
ChainCfgPath: chainCfgPath,
322+
DataDirPath: dataDirPath,
323+
TestDirPath: testDir,
324+
}
325+
326+
return internalOptions, nil
327+
}

0 commit comments

Comments
 (0)