Skip to content
Open
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
11 changes: 11 additions & 0 deletions .github/workflows/_go-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,9 @@ on:
run-pebble-b:
required: false
type: boolean
run-experimental:
required: false
type: boolean

jobs:
go-tests:
Expand Down Expand Up @@ -191,6 +194,14 @@ jobs:
${{ github.workspace }}/.github/workflows/gotestsum.sh --timeout 90m --test_database_engine pebble
--junitfile test-results/junit-pebble-b.xml --skip '^Test[A-N]' --reduce-parallelism

# --------------------- EXPERIMENTAL MODE ---------------------

- name: run experimental tests
if: inputs.run-experimental
run: >-
${{ github.workspace }}/.github/workflows/gotestsum.sh
--tags experimental --run '^TestBenchmarkingSequencer' --timeout 60m --cover

# --------------------- PROCESS JUNIT LOGS ---------------------

- name: Process JUnit XML logs
Expand Down
3 changes: 2 additions & 1 deletion .github/workflows/_standard-go-test-suite.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ jobs:
strategy:
fail-fast: false
matrix:
test-mode: [defaults-A, defaults-B, flaky, pathdb, challenge, stylus, l3challenge]
test-mode: [defaults-A, defaults-B, flaky, pathdb, challenge, stylus, l3challenge, experimental]
uses: ./.github/workflows/_go-tests.yml
secrets: inherit
with:
Expand All @@ -20,3 +20,4 @@ jobs:
run-challenge: ${{ matrix.test-mode == 'challenge' }}
run-stylus: ${{ matrix.test-mode == 'stylus' }}
run-l3challenge: ${{ matrix.test-mode == 'l3challenge' }}
run-experimental: ${{ matrix.test-mode == 'experimental' }}
13 changes: 12 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,7 @@ all: build build-replay-env test-gen-proofs
@touch .make/all

.PHONY: build
build: $(patsubst %,$(output_root)/bin/%, nitro deploy relay daprovider anytrustserver autonomous-auctioneer bidder-client anytrusttool blobtool el-proxy mockexternalsigner seq-coordinator-invalidate nitro-val seq-coordinator-manager dbconv genesis-generator transaction-filterer)
build: $(patsubst %,$(output_root)/bin/%, nitro deploy relay daprovider anytrustserver autonomous-auctioneer bidder-client anytrusttool blobtool el-proxy mockexternalsigner seq-coordinator-invalidate nitro-val seq-coordinator-manager dbconv genesis-generator transaction-filterer nitro-experimental)
@printf $(done)

.PHONY: build-node-deps
Expand Down Expand Up @@ -250,6 +250,13 @@ test-go-redis: test-go-deps
.github/workflows/gotestsum.sh --timeout 120m --run TestRedis --nolog -- --test_redis=redis://localhost:6379/0
@printf $(done)

.PHONY: test-go-experimental
test-go-experimental: test-go-deps
.github/workflows/gotestsum.sh --timeout 120m --run '^TestBenchmarkingSequencer' --tags experimental --nolog
@printf $(done)



.PHONY: test-gen-proofs
test-gen-proofs: \
$(arbitrator_test_wasms) \
Expand Down Expand Up @@ -314,6 +321,10 @@ check-license-headers:
$(output_root)/bin/nitro: $(DEP_PREDICATE) build-node-deps
go build $(GOLANG_PARAMS) -o $@ "$(CURDIR)/cmd/nitro"

# nitro built with benchmarking sequencer tooling enabled (requires the experimental build tag)
$(output_root)/bin/nitro-experimental: $(DEP_PREDICATE) build-node-deps
go build $(GOLANG_PARAMS) --tags experimental -o $@ "$(CURDIR)/cmd/nitro"

$(output_root)/bin/deploy: $(DEP_PREDICATE) build-node-deps
go build $(GOLANG_PARAMS) -o $@ "$(CURDIR)/cmd/deploy"

Expand Down
5 changes: 5 additions & 0 deletions changelog/kolbyml-nit-4481.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
### Configuration
- Add `--execution.dangerous.benchmarking-sequencer.enable` to enable the benchmarking sequencer RPC (only available in builds with the `experimental` build tag).

### Internal
Comment thread
gligneul marked this conversation as resolved.
- Add benchmarking sequencer RPC (`benchseq`) and system tests behind the `experimental` build tag.
2 changes: 1 addition & 1 deletion cmd/util/confighelpers/configuration.go
Original file line number Diff line number Diff line change
Expand Up @@ -209,7 +209,7 @@ func devFlagArgs() []string {
"--init.empty=false",
"--http.port", "8547",
"--http.addr", "127.0.0.1",
"--http.api=net,web3,eth,arb,arbdebug,debug",
"--http.api=net,web3,eth,arb,arbdebug,debug,benchseq",

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thinking out loud - in Rust, I'd definitely recommend:

#[cfg(feature = "experimental")]
"--http.api=net,web3,eth,arb,arbdebug,debug,benchseq",
#[cfg(not(feature = "experimental"))]
"--http.api=net,web3,eth,arb,arbdebug,debug",

but in Go, I'm wondering if we should somehow tag-gate this api, so that user's won't see unsupported api in the list

"--node.transaction-streamer.track-block-metadata-from=1",
}
return args
Expand Down
93 changes: 93 additions & 0 deletions execution/gethexec/bench_sequencer.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
// Copyright 2026, Offchain Labs, Inc.
// For license information, see https://github.com/OffchainLabs/nitro/blob/master/LICENSE.md

//go:build experimental

package gethexec

import (
"context"

"github.com/ethereum/go-ethereum/log"
"github.com/offchainlabs/nitro/util/containers"
"github.com/offchainlabs/nitro/util/stopwaiter"
"github.com/spf13/pflag"
)

func BenchmarkingSequencerConfigAddOptions(prefix string, f *pflag.FlagSet) {
f.Bool(prefix+".enable", BenchmarkingSequencerConfigDefault.Enable, "enable benchmarking sequencer RPC (manual block creation; requires experimental build tag)")
}

func (c *BenchmarkingSequencerConfig) Validate() error {
if c.Enable {
log.Warn("DANGER! benchmarking sequencer enabled (manual block creation); do not use in production")
}
return nil
}

func NewBenchmarkingSequencer(sequencer *Sequencer) (TransactionPublisher, interface{}) {
benchmarkingSequencer := &BenchmarkingSequencer{
Sequencer: sequencer,
semaphore: make(chan struct{}, 1),
}
return benchmarkingSequencer, NewBenchmarkingSequencerAPI(benchmarkingSequencer)
}

type BenchmarkingSequencer struct {
*Sequencer
semaphore chan struct{}
}

func (s *BenchmarkingSequencer) Start(ctx context.Context) error {
// override Sequencer.Start to not start the inner sequencer

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if we don't start the inner sequencer, how can createBlock succeed? shouldn't we run the logic from sequencer.go:Start() at some point?

s.StopWaiter.Start(ctx, s)
s.semaphore <- struct{}{}
return nil
}

func (s *BenchmarkingSequencer) TxQueueLength(includeRetryTxQueue bool) int {
if includeRetryTxQueue {
return len(s.Sequencer.txQueue) + s.Sequencer.txRetryQueue.Len()
}
return len(s.Sequencer.txQueue)
}

func (s *BenchmarkingSequencer) TxRetryQueueLength() int {
return s.Sequencer.txRetryQueue.Len()
}
Comment on lines +48 to +57

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should we make reads somehow synchronized / mutexed? I'm not sure if we can have some race condition here


func (s *BenchmarkingSequencer) CreateBlock() containers.PromiseInterface[bool] {
return stopwaiter.LaunchPromiseThread[bool](s, func(ctx context.Context) (bool, error) {
select {
// createBlock can't be run in parallel
case <-s.semaphore:
defer func() {
// release semaphore, also in case of panic
s.semaphore <- struct{}{}
}()
return s.createBlock(ctx), nil
case <-ctx.Done():
return false, ctx.Err()
}
})
}

type BenchmarkingSequencerAPI struct {
benchmarkingSequencer *BenchmarkingSequencer
}

func (a *BenchmarkingSequencerAPI) TxQueueLength(includeRetryTxQueue bool) int {
return a.benchmarkingSequencer.TxQueueLength(includeRetryTxQueue)
}

func (a *BenchmarkingSequencerAPI) TxRetryQueueLength() int {
return a.benchmarkingSequencer.TxRetryQueueLength()
}

func (a *BenchmarkingSequencerAPI) CreateBlock(ctx context.Context) (bool, error) {
return a.benchmarkingSequencer.CreateBlock().Await(ctx)
}

func NewBenchmarkingSequencerAPI(benchmarkingSequencer *BenchmarkingSequencer) *BenchmarkingSequencerAPI {
return &BenchmarkingSequencerAPI{benchmarkingSequencer: benchmarkingSequencer}
}
15 changes: 15 additions & 0 deletions execution/gethexec/bench_sequencer_config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// Copyright 2026, Offchain Labs, Inc.
// For license information, see https://github.com/OffchainLabs/nitro/blob/master/LICENSE.md

// DANGER! this file is included in all builds
// DANGER! do not place any experimental tag logic here
Comment on lines +4 to +5

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do you find these warnings helpful? I guess that nitro just won't compile, if one adds the experimental tag here

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe the comment means not to add experimental logic here because it is included in all builds.


package gethexec

type BenchmarkingSequencerConfig struct {
Enable bool `koanf:"enable"`
}

var BenchmarkingSequencerConfigDefault = BenchmarkingSequencerConfig{
Enable: false,
}
28 changes: 28 additions & 0 deletions execution/gethexec/bench_sequencer_stub.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// Copyright 2026, Offchain Labs, Inc.
// For license information, see https://github.com/OffchainLabs/nitro/blob/master/LICENSE.md

//go:build !experimental

package gethexec

import (
"github.com/spf13/pflag"

"github.com/ethereum/go-ethereum/log"
)

func BenchmarkingSequencerConfigAddOptions(_ string, _ *pflag.FlagSet) {
// don't add any options
}

func (c *BenchmarkingSequencerConfig) Validate() error {
if c.Enable {
log.Warn("benchmarking sequencer requested but not supported in this build (missing experimental build tag)")

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd consider returning an error here in case someone explicitly requests benchmarking, but doesn't add the tag

}
return nil
}

func NewBenchmarkingSequencer(sequencer *Sequencer) (TransactionPublisher, interface{}) {
// do nothing
return sequencer, nil
}
37 changes: 37 additions & 0 deletions execution/gethexec/node.go
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,7 @@ type Config struct {
ExposeMultiGas bool `koanf:"expose-multi-gas"`
RPCServer rpcserver.Config `koanf:"rpc-server"`
ConsensusRPCClient rpcclient.ClientConfig `koanf:"consensus-rpc-client" reload:"hot"`
Dangerous DangerousConfig `koanf:"dangerous"`

forwardingTarget string
}
Expand Down Expand Up @@ -168,6 +169,9 @@ func (c *Config) Validate() error {
if err := c.ConsensusRPCClient.Validate(); err != nil {
return fmt.Errorf("error validating ConsensusRPCClient config: %w", err)
}
if err := c.Dangerous.Validate(); err != nil {
return err

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just for consistency, you can add context for the error, as the checks above do

}
return nil
}

Expand All @@ -191,6 +195,7 @@ func ConfigAddOptions(prefix string, f *pflag.FlagSet) {
LiveTracingConfigAddOptions(prefix+".vmtrace", f)
rpcserver.ConfigAddOptions(prefix+".rpc-server", "execution", f)
rpcclient.RPCClientAddOptions(prefix+".consensus-rpc-client", f, &ConfigDefault.ConsensusRPCClient)
DangerousConfigAddOptions(prefix+".dangerous", f)
}

type LiveTracingConfig struct {
Expand All @@ -208,6 +213,25 @@ func LiveTracingConfigAddOptions(prefix string, f *pflag.FlagSet) {
f.String(prefix+".json-config", DefaultLiveTracingConfig.JSONConfig, "(experimental) Tracer configuration in JSON format")
}

type DangerousConfig struct {
BenchmarkingSequencer BenchmarkingSequencerConfig `koanf:"benchmarking-sequencer"`
}

var DefaultDangerousConfig = DangerousConfig{
BenchmarkingSequencer: BenchmarkingSequencerConfigDefault,
}

func DangerousConfigAddOptions(prefix string, f *pflag.FlagSet) {
BenchmarkingSequencerConfigAddOptions(prefix+".benchmarking-sequencer", f)
}

func (c *DangerousConfig) Validate() error {
if err := c.BenchmarkingSequencer.Validate(); err != nil {
return err
}
return nil
Comment on lines +229 to +232

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe for now:

Suggested change
if err := c.BenchmarkingSequencer.Validate(); err != nil {
return err
}
return nil
return c.BenchmarkingSequencer.Validate()

}

var ConfigDefault = Config{
RPC: arbitrum.DefaultConfig,
TxIndexer: DefaultTxIndexerConfig,
Expand Down Expand Up @@ -237,6 +261,7 @@ var ConfigDefault = Config{
ArgLogLimit: 2048,
WebsocketMessageSizeLimit: 256 * 1024 * 1024,
},
Dangerous: DefaultDangerousConfig,
}

type ConfigFetcher interface {
Expand Down Expand Up @@ -300,13 +325,17 @@ func CreateExecutionNode(
log.Warn("sequencer enabled without l1 client")
}

var benchmarkingSequencerService interface{}
if config.Sequencer.Enable {
seqConfigFetcher := func() *SequencerConfig { return &configFetcher.Get().Sequencer }
sequencer, err = NewSequencer(execEngine, parentChainReader, seqConfigFetcher, parentChainID)
if err != nil {
return nil, err
}
txPublisher = sequencer
if config.Dangerous.BenchmarkingSequencer.Enable {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we don't need this condition, do we?

txPublisher, benchmarkingSequencerService = NewBenchmarkingSequencer(sequencer)
}
} else {
if config.Forwarder.RedisUrl != "" {
txPublisher = NewRedisTxForwarder(config.forwardingTarget, &config.Forwarder)
Expand Down Expand Up @@ -438,6 +467,14 @@ func CreateExecutionNode(
})
}

if benchmarkingSequencerService != nil {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we run into a typed nil here?

apis = append(apis, rpc.API{
Namespace: "benchseq",
Service: benchmarkingSequencerService,
Public: false,
})
}

stack.RegisterAPIs(apis)

return execNode, nil
Expand Down
26 changes: 13 additions & 13 deletions execution/gethexec/sequencer.go
Original file line number Diff line number Diff line change
Expand Up @@ -95,12 +95,17 @@ type SequencerConfig struct {
ExpectedSurplusHardThreshold string `koanf:"expected-surplus-hard-threshold" reload:"hot"`
EnableProfiling bool `koanf:"enable-profiling" reload:"hot"`
Timeboost TimeboostConfig `koanf:"timeboost"`
Dangerous DangerousConfig `koanf:"dangerous"`
Dangerous SequencerDangerousConfig `koanf:"dangerous"`
TransactionFiltering TransactionFilteringConfig `koanf:"transaction-filtering" reload:"hot"`
expectedSurplusSoftThreshold int
expectedSurplusHardThreshold int
}

type SequencerDangerousConfig struct {
DisableSeqInboxMaxDataSizeCheck bool `koanf:"disable-seq-inbox-max-data-size-check"`
DisableBlobBaseFeeCheck bool `koanf:"disable-blob-base-fee-check"`
}

type TransactionFilteringConfig struct {
DisableDelayedSequencingFilter bool `koanf:"disable-delayed-sequencing-filter"`
EventFilter eventfilter.EventFilterConfig `koanf:"event-filter"`
Expand Down Expand Up @@ -135,11 +140,6 @@ func TransactionFilteringConfigAddOptions(prefix string, f *pflag.FlagSet) {
rpcclient.RPCClientAddOptions(prefix+".transaction-filterer-rpc-client", f, &DefaultTransactionFilteringConfig.TransactionFiltererRPCClient)
}

type DangerousConfig struct {
DisableSeqInboxMaxDataSizeCheck bool `koanf:"disable-seq-inbox-max-data-size-check"`
DisableBlobBaseFeeCheck bool `koanf:"disable-blob-base-fee-check"`
}

type TimeboostConfig struct {
Enable bool `koanf:"enable"`
AuctionContractAddress string `koanf:"auction-contract-address"`
Expand Down Expand Up @@ -261,11 +261,11 @@ var DefaultSequencerConfig = SequencerConfig{
ExpectedSurplusHardThreshold: "default",
EnableProfiling: false,
Timeboost: DefaultTimeboostConfig,
Dangerous: DefaultDangerousConfig,
Dangerous: DefaultSequencerDangerousConfig,
TransactionFiltering: DefaultTransactionFilteringConfig,
}

var DefaultDangerousConfig = DangerousConfig{
var DefaultSequencerDangerousConfig = SequencerDangerousConfig{
DisableSeqInboxMaxDataSizeCheck: false,
}

Expand All @@ -279,7 +279,8 @@ func SequencerConfigAddOptions(prefix string, f *pflag.FlagSet) {
AddOptionsForSequencerForwarderConfig(prefix+".forwarder", f)
TimeboostAddOptions(prefix+".timeboost", f)

DangerousAddOptions(prefix+".dangerous", f)
SequencerDangerousAddOptions(prefix+".dangerous", f)
TransactionFilteringConfigAddOptions(prefix+".transaction-filtering", f)
f.Int(prefix+".queue-size", DefaultSequencerConfig.QueueSize, "size of the pending tx queue")
f.Duration(prefix+".queue-timeout", DefaultSequencerConfig.QueueTimeout, "maximum amount of time transaction can wait in queue")
f.Int(prefix+".nonce-cache-size", DefaultSequencerConfig.NonceCacheSize, "size of the tx sender nonce cache")
Expand All @@ -290,7 +291,6 @@ func SequencerConfigAddOptions(prefix string, f *pflag.FlagSet) {
f.String(prefix+".expected-surplus-soft-threshold", DefaultSequencerConfig.ExpectedSurplusSoftThreshold, "if expected surplus is lower than this value, warnings are posted")
f.String(prefix+".expected-surplus-hard-threshold", DefaultSequencerConfig.ExpectedSurplusHardThreshold, "if expected surplus is lower than this value, new incoming transactions will be denied")
f.Bool(prefix+".enable-profiling", DefaultSequencerConfig.EnableProfiling, "enable CPU profiling and tracing")
TransactionFilteringConfigAddOptions(prefix+".transaction-filtering", f)
}

func TimeboostAddOptions(prefix string, f *pflag.FlagSet) {
Expand All @@ -306,9 +306,9 @@ func TimeboostAddOptions(prefix string, f *pflag.FlagSet) {
f.Uint64(prefix+".queue-timeout-in-blocks", DefaultTimeboostConfig.QueueTimeoutInBlocks, "maximum amount of time (measured in blocks) that Express Lane transactions can wait in the sequencer's queue")
}

func DangerousAddOptions(prefix string, f *pflag.FlagSet) {
f.Bool(prefix+".disable-seq-inbox-max-data-size-check", DefaultDangerousConfig.DisableSeqInboxMaxDataSizeCheck, "DANGEROUS! disables nitro checks on sequencer MaxTxDataSize against the sequencer inbox MaxDataSize")
f.Bool(prefix+".disable-blob-base-fee-check", DefaultDangerousConfig.DisableBlobBaseFeeCheck, "DANGEROUS! disables nitro checks on sequencer for blob base fee")
func SequencerDangerousAddOptions(prefix string, f *pflag.FlagSet) {
f.Bool(prefix+".disable-seq-inbox-max-data-size-check", DefaultSequencerDangerousConfig.DisableSeqInboxMaxDataSizeCheck, "DANGEROUS! disables nitro checks on sequencer MaxTxDataSize against the sequencer inbox MaxDataSize")
f.Bool(prefix+".disable-blob-base-fee-check", DefaultSequencerDangerousConfig.DisableBlobBaseFeeCheck, "DANGEROUS! disables nitro checks on sequencer for blob base fee")
}

func EventFilterAddOptions(prefix string, f *pflag.FlagSet) {
Expand Down
2 changes: 1 addition & 1 deletion go-ethereum
Loading
Loading