Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,8 @@ Each test executes a standardized workflow:

This approach allows precise measurement of performance characteristics for both block production and validation.

Benchmarks run both phases by default. Set `roles: [sequencer]` on a benchmark definition to run only the sequencer/block-building phase, which is useful for snapshot startup and load-test coverage that does not need validator payload replay.

## Configuration

### Available Flags
Expand Down
2 changes: 2 additions & 0 deletions configs/examples/snapshot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ benchmarks:
# just delete the snapshot directory to force a full copy
command: ./scripts/copy-local-snapshot.sh --skip-if-nonempty
genesis_file: ../../sepolia-alpha/sepolia-alpha-genesis.json
roles:
- sequencer
# force_clean is true by default to ensure consistency, but we can skip it for testing
force_clean: false
variables:
Expand Down
23 changes: 22 additions & 1 deletion docs/benchmark-types.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,28 @@
- Collect block metrics
- Reason we don't need to test mempool for validating node: only used for tx gossip, no logic actually has to be executed

## Role selection

Benchmark definitions run both roles by default:

```yaml
benchmarks:
- variables:
# ...
```

Set `roles: [sequencer]` when a benchmark only needs block-building or snapshot startup coverage and does not need to validate the generated payloads:

```yaml
benchmarks:
- roles: [sequencer]
variables:
# ...
```

The validator role cannot run without the sequencer role because validator benchmarks consume payloads produced by the sequencer phase. Proof-program benchmarks also require the validator role.

## op-challenger test

- batch all blocks in the test to L1
- run op-program on those batches - verify output root
- run op-program on those batches - verify output root
92 changes: 92 additions & 0 deletions runner/benchmark/definition.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,83 @@ import (
"github.com/base/base-bench/runner/payload"
)

type BenchmarkRole string

const (
BenchmarkRoleSequencer BenchmarkRole = "sequencer"
BenchmarkRoleValidator BenchmarkRole = "validator"
)

var defaultBenchmarkRoles = []BenchmarkRole{BenchmarkRoleSequencer, BenchmarkRoleValidator}

func DefaultBenchmarkRoles() []BenchmarkRole {
return append([]BenchmarkRole(nil), defaultBenchmarkRoles...)
}

func NormalizeBenchmarkRoles(roles []BenchmarkRole) ([]BenchmarkRole, error) {
if len(roles) == 0 {
return DefaultBenchmarkRoles(), nil
}

seen := make(map[BenchmarkRole]bool, len(roles))
for _, role := range roles {
switch role {
case BenchmarkRoleSequencer, BenchmarkRoleValidator:
default:
return nil, fmt.Errorf("invalid benchmark role %q", role)
}

if seen[role] {
return nil, fmt.Errorf("duplicate benchmark role %q", role)
}
seen[role] = true
}

if !seen[BenchmarkRoleSequencer] {
return nil, fmt.Errorf("benchmark roles must include %q", BenchmarkRoleSequencer)
}

normalized := []BenchmarkRole{BenchmarkRoleSequencer}
if seen[BenchmarkRoleValidator] {
normalized = append(normalized, BenchmarkRoleValidator)
}

return normalized, nil
}

func BenchmarkRolesContain(roles []BenchmarkRole, role BenchmarkRole) bool {
for _, r := range roles {
if r == role {
return true
}
}
return false
}

func BenchmarkRoleNames(roles []BenchmarkRole) []string {
names := make([]string, 0, len(roles))
for _, role := range roles {
names = append(names, string(role))
}
return names
}

func BenchmarkRolesString(roles []BenchmarkRole) string {
return strings.Join(BenchmarkRoleNames(roles), ",")
}

func IsDefaultBenchmarkRoles(roles []BenchmarkRole) bool {
if len(roles) != len(defaultBenchmarkRoles) {
return false
}
for i, role := range roles {
if role != defaultBenchmarkRoles[i] {
return false
}
}
return true
}

// Param is a single dimension of a benchmark matrix. It can be a
// single value or a list of values.
type Param struct {
Expand Down Expand Up @@ -134,11 +211,22 @@ type TestDefinition struct {
Snapshot *SnapshotDefinition `yaml:"snapshot"`
Metrics *ThresholdConfig `yaml:"metrics"`
Tags *map[string]string `yaml:"tags"`
Roles []BenchmarkRole `yaml:"roles"`
Variables []Param `yaml:"variables"`
ProofProgram *ProofProgramOptions `yaml:"proof_program"`
}

func (bc *TestDefinition) Check() error {
roles, err := NormalizeBenchmarkRoles(bc.Roles)
if err != nil {
return err
}

proofProgramEnabled := bc.ProofProgram != nil && (bc.ProofProgram.Enabled == nil || *bc.ProofProgram.Enabled)
if proofProgramEnabled && !BenchmarkRolesContain(roles, BenchmarkRoleValidator) {
return errors.New("proof_program requires the validator benchmark role")
}

for _, b := range bc.Variables {
err := b.Check()
if err != nil {
Expand All @@ -147,3 +235,7 @@ func (bc *TestDefinition) Check() error {
}
return nil
}

func (bc *TestDefinition) NormalizedRoles() ([]BenchmarkRole, error) {
return NormalizeBenchmarkRoles(bc.Roles)
}
11 changes: 11 additions & 0 deletions runner/benchmark/matrix.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,19 @@ type TestPlan struct {
Snapshot *SnapshotDefinition
ProofProgram *ProofProgramOptions
Thresholds *ThresholdConfig
Roles []BenchmarkRole
}

func NewTestPlanFromConfig(c TestDefinition, testFileName string, config *BenchmarkConfig) (*TestPlan, error) {
if err := c.Check(); err != nil {
return nil, err
}

roles, err := c.NormalizedRoles()
if err != nil {
return nil, err
}

testRuns, err := ResolveTestRunsFromMatrix(c, testFileName, config)
if err != nil {
return nil, err
Expand All @@ -42,6 +52,7 @@ func NewTestPlanFromConfig(c TestDefinition, testFileName string, config *Benchm
Snapshot: c.Snapshot,
ProofProgram: proofProgram,
Thresholds: c.Metrics,
Roles: roles,
}, nil
}

Expand Down
105 changes: 105 additions & 0 deletions runner/benchmark/matrix_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,111 @@ func TestResolveTestRunsFromMatrix(t *testing.T) {
}
}

func TestNewTestPlanFromConfigRoles(t *testing.T) {
config := &benchmark.BenchmarkConfig{Name: "test"}
definition := benchmark.TestDefinition{
Roles: []benchmark.BenchmarkRole{benchmark.BenchmarkRoleSequencer},
Variables: []benchmark.Param{
{
ParamType: "payload",
Value: "simple",
},
},
}

plan, err := benchmark.NewTestPlanFromConfig(definition, "config.yml", config)
require.NoError(t, err)
require.Equal(t, []benchmark.BenchmarkRole{benchmark.BenchmarkRoleSequencer}, plan.Roles)

metadata := benchmark.RunGroupFromTestPlans([]benchmark.TestPlan{*plan}, nil)
require.Len(t, metadata.Runs, 1)
require.Equal(t, "sequencer", metadata.Runs[0].TestConfig["Roles"])
}

func TestNewTestPlanFromConfigDefaultsToBothRoles(t *testing.T) {
config := &benchmark.BenchmarkConfig{Name: "test"}
definition := benchmark.TestDefinition{
Variables: []benchmark.Param{
{
ParamType: "payload",
Value: "simple",
},
},
}

plan, err := benchmark.NewTestPlanFromConfig(definition, "config.yml", config)
require.NoError(t, err)
require.Equal(t, []benchmark.BenchmarkRole{
benchmark.BenchmarkRoleSequencer,
benchmark.BenchmarkRoleValidator,
}, plan.Roles)

metadata := benchmark.RunGroupFromTestPlans([]benchmark.TestPlan{*plan}, nil)
require.Len(t, metadata.Runs, 1)
require.NotContains(t, metadata.Runs[0].TestConfig, "Roles")
}

func TestNewTestPlanFromConfigRejectsInvalidRoles(t *testing.T) {
tests := []struct {
name string
roles []benchmark.BenchmarkRole
}{
{
name: "unknown role",
roles: []benchmark.BenchmarkRole{"other"},
},
{
name: "duplicate role",
roles: []benchmark.BenchmarkRole{benchmark.BenchmarkRoleSequencer, benchmark.BenchmarkRoleSequencer},
},
{
name: "validator without sequencer",
roles: []benchmark.BenchmarkRole{benchmark.BenchmarkRoleValidator},
},
}

config := &benchmark.BenchmarkConfig{Name: "test"}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
definition := benchmark.TestDefinition{
Roles: tt.roles,
Variables: []benchmark.Param{
{
ParamType: "payload",
Value: "simple",
},
},
}

_, err := benchmark.NewTestPlanFromConfig(definition, "config.yml", config)
require.Error(t, err)
})
}
}

func TestNewTestPlanFromConfigRejectsProofProgramWithoutValidator(t *testing.T) {
config := &benchmark.BenchmarkConfig{Name: "test"}
definition := benchmark.TestDefinition{
Roles: []benchmark.BenchmarkRole{benchmark.BenchmarkRoleSequencer},
ProofProgram: &benchmark.ProofProgramOptions{
Enabled: boolPtr(true),
},
Variables: []benchmark.Param{
{
ParamType: "payload",
Value: "simple",
},
},
}

_, err := benchmark.NewTestPlanFromConfig(definition, "config.yml", config)
require.ErrorContains(t, err, "proof_program requires the validator benchmark role")
}

func stringPtr(s string) *string {
return &s
}

func boolPtr(b bool) *bool {
return &b
}
21 changes: 15 additions & 6 deletions runner/benchmark/result_metadata.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,11 @@ import (
)

type RunResult struct {
Success bool `json:"success"`
Complete bool `json:"complete"`
SequencerMetrics types.SequencerKeyMetrics `json:"sequencerMetrics"`
ValidatorMetrics types.ValidatorKeyMetrics `json:"validatorMetrics"`
ClientVersion string `json:"clientVersion,omitempty"`
Success bool `json:"success"`
Complete bool `json:"complete"`
SequencerMetrics *types.SequencerKeyMetrics `json:"sequencerMetrics,omitempty"`
ValidatorMetrics *types.ValidatorKeyMetrics `json:"validatorMetrics,omitempty"`
ClientVersion string `json:"clientVersion,omitempty"`
}

// MachineInfo contains information about the machine running the benchmark
Expand Down Expand Up @@ -61,13 +61,22 @@ func RunGroupFromTestPlans(testPlans []TestPlan, machineInfo *MachineInfo) RunGr
}

for _, testPlan := range testPlans {
roles := testPlan.Roles
if len(roles) == 0 {
roles = DefaultBenchmarkRoles()
}
for _, params := range testPlan.Runs {
testConfig := params.Params.ToConfig()
if !IsDefaultBenchmarkRoles(roles) {
testConfig["Roles"] = BenchmarkRolesString(roles)
}

metadata.Runs = append(metadata.Runs, Run{
ID: params.ID,
SourceFile: params.TestFile,
TestName: params.Name,
TestDescription: params.Description,
TestConfig: params.Params.ToConfig(),
TestConfig: testConfig,
OutputDir: params.OutputDir,
Thresholds: testPlan.Thresholds,
CreatedAt: &now,
Expand Down
Loading
Loading