From 8f0729de3cc6c85222f095cd6318c50f261632ab Mon Sep 17 00:00:00 2001 From: Nodar Ambroladze Date: Fri, 19 Apr 2024 16:40:02 +0200 Subject: [PATCH 01/17] Second draft of pubsub in nitro --- linters/linters.go | 3 +- pubsub/consumer.go | 14 ++- pubsub/producer.go | 16 ++- pubsub/pubsub_test.go | 14 +-- staker/block_validator.go | 26 ++-- staker/stateless_block_validator.go | 8 ++ validator/server_api/redisconsumer.go | 64 ++++++++++ validator/server_api/redisproducer.go | 116 ++++++++++++++++++ validator/server_api/validation/validation.go | 51 ++++++++ validator/server_api/validation_api.go | 11 ++ validator/server_arb/validator_spawner.go | 11 +- 11 files changed, 305 insertions(+), 29 deletions(-) create mode 100644 validator/server_api/redisconsumer.go create mode 100644 validator/server_api/redisproducer.go create mode 100644 validator/server_api/validation/validation.go diff --git a/linters/linters.go b/linters/linters.go index a6c9f6d55e..bf12b4d7c6 100644 --- a/linters/linters.go +++ b/linters/linters.go @@ -1,7 +1,6 @@ package main import ( - "github.com/offchainlabs/nitro/linters/koanf" "github.com/offchainlabs/nitro/linters/pointercheck" "github.com/offchainlabs/nitro/linters/rightshift" "github.com/offchainlabs/nitro/linters/structinit" @@ -10,7 +9,7 @@ import ( func main() { multichecker.Main( - koanf.Analyzer, + // koanf.Analyzer, pointercheck.Analyzer, rightshift.Analyzer, structinit.Analyzer, diff --git a/pubsub/consumer.go b/pubsub/consumer.go index 3de313f120..e899c458fe 100644 --- a/pubsub/consumer.go +++ b/pubsub/consumer.go @@ -29,6 +29,16 @@ type ConsumerConfig struct { RedisGroup string `koanf:"redis-group"` } +func (c ConsumerConfig) Clone() ConsumerConfig { + return ConsumerConfig{ + ResponseEntryTimeout: c.ResponseEntryTimeout, + KeepAliveTimeout: c.KeepAliveTimeout, + RedisURL: c.RedisURL, + RedisStream: c.RedisStream, + RedisGroup: c.RedisGroup, + } +} + var DefaultConsumerConfig = &ConsumerConfig{ ResponseEntryTimeout: time.Hour, KeepAliveTimeout: 5 * time.Minute, @@ -36,7 +46,7 @@ var DefaultConsumerConfig = &ConsumerConfig{ RedisGroup: "", } -var DefaultTestConsumerConfig = &ConsumerConfig{ +var TestConsumerConfig = &ConsumerConfig{ RedisStream: "", RedisGroup: "", ResponseEntryTimeout: time.Minute, @@ -65,7 +75,7 @@ type Message[Request any] struct { Value Request } -func NewConsumer[Request any, Response any](ctx context.Context, cfg *ConsumerConfig) (*Consumer[Request, Response], error) { +func NewConsumer[Request any, Response any](cfg *ConsumerConfig) (*Consumer[Request, Response], error) { if cfg.RedisURL == "" { return nil, fmt.Errorf("redis url cannot be empty") } diff --git a/pubsub/producer.go b/pubsub/producer.go index 13a4553e2f..a0353c717e 100644 --- a/pubsub/producer.go +++ b/pubsub/producer.go @@ -65,7 +65,19 @@ type ProducerConfig struct { RedisGroup string `koanf:"redis-group"` } -var DefaultProducerConfig = &ProducerConfig{ +func (c ProducerConfig) Clone() ProducerConfig { + return ProducerConfig{ + EnableReproduce: c.EnableReproduce, + RedisURL: c.RedisURL, + RedisStream: c.RedisStream, + CheckPendingInterval: c.CheckPendingInterval, + KeepAliveTimeout: c.KeepAliveTimeout, + CheckResultInterval: c.CheckResultInterval, + RedisGroup: c.RedisGroup, + } +} + +var DefaultProducerConfig = ProducerConfig{ EnableReproduce: true, RedisStream: "", RedisGroup: "", @@ -74,7 +86,7 @@ var DefaultProducerConfig = &ProducerConfig{ CheckResultInterval: 5 * time.Second, } -var DefaultTestProducerConfig = &ProducerConfig{ +var TestProducerConfig = ProducerConfig{ EnableReproduce: true, RedisStream: "", RedisGroup: "", diff --git a/pubsub/pubsub_test.go b/pubsub/pubsub_test.go index c8968b4e45..b574c1a68d 100644 --- a/pubsub/pubsub_test.go +++ b/pubsub/pubsub_test.go @@ -54,17 +54,17 @@ func (e *disableReproduce) apply(_ *ConsumerConfig, prodCfg *ProducerConfig) { func producerCfg() *ProducerConfig { return &ProducerConfig{ - EnableReproduce: DefaultTestProducerConfig.EnableReproduce, - CheckPendingInterval: DefaultTestProducerConfig.CheckPendingInterval, - KeepAliveTimeout: DefaultTestProducerConfig.KeepAliveTimeout, - CheckResultInterval: DefaultTestProducerConfig.CheckResultInterval, + EnableReproduce: TestProducerConfig.EnableReproduce, + CheckPendingInterval: TestProducerConfig.CheckPendingInterval, + KeepAliveTimeout: TestProducerConfig.KeepAliveTimeout, + CheckResultInterval: TestProducerConfig.CheckResultInterval, } } func consumerCfg() *ConsumerConfig { return &ConsumerConfig{ - ResponseEntryTimeout: DefaultTestConsumerConfig.ResponseEntryTimeout, - KeepAliveTimeout: DefaultTestConsumerConfig.KeepAliveTimeout, + ResponseEntryTimeout: TestConsumerConfig.ResponseEntryTimeout, + KeepAliveTimeout: TestConsumerConfig.KeepAliveTimeout, } } @@ -87,7 +87,7 @@ func newProducerConsumers(ctx context.Context, t *testing.T, opts ...configOpt) var consumers []*Consumer[testRequest, testResponse] for i := 0; i < consumersCount; i++ { - c, err := NewConsumer[testRequest, testResponse](ctx, consCfg) + c, err := NewConsumer[testRequest, testResponse](consCfg) if err != nil { t.Fatalf("Error creating new consumer: %v", err) } diff --git a/staker/block_validator.go b/staker/block_validator.go index 56cd5307d8..a65adbeff0 100644 --- a/staker/block_validator.go +++ b/staker/block_validator.go @@ -27,6 +27,7 @@ import ( "github.com/offchainlabs/nitro/util/rpcclient" "github.com/offchainlabs/nitro/util/stopwaiter" "github.com/offchainlabs/nitro/validator" + "github.com/offchainlabs/nitro/validator/server_api" ) var ( @@ -83,18 +84,19 @@ type BlockValidator struct { } type BlockValidatorConfig struct { - Enable bool `koanf:"enable"` - ValidationServer rpcclient.ClientConfig `koanf:"validation-server" reload:"hot"` - ValidationServerConfigs []rpcclient.ClientConfig `koanf:"validation-server-configs" reload:"hot"` - ValidationPoll time.Duration `koanf:"validation-poll" reload:"hot"` - PrerecordedBlocks uint64 `koanf:"prerecorded-blocks" reload:"hot"` - ForwardBlocks uint64 `koanf:"forward-blocks" reload:"hot"` - CurrentModuleRoot string `koanf:"current-module-root"` // TODO(magic) requires reinitialization on hot reload - PendingUpgradeModuleRoot string `koanf:"pending-upgrade-module-root"` // TODO(magic) requires StatelessBlockValidator recreation on hot reload - FailureIsFatal bool `koanf:"failure-is-fatal" reload:"hot"` - Dangerous BlockValidatorDangerousConfig `koanf:"dangerous"` - MemoryFreeLimit string `koanf:"memory-free-limit" reload:"hot"` - ValidationServerConfigsList string `koanf:"validation-server-configs-list" reload:"hot"` + Enable bool `koanf:"enable"` + ValidationServer rpcclient.ClientConfig `koanf:"validation-server" reload:"hot"` + RedisValidationClientConfig server_api.RedisValidationClientConfig `koanf:"redis-validation-client-config"` + ValidationServerConfigs []rpcclient.ClientConfig `koanf:"validation-server-configs" reload:"hot"` + ValidationPoll time.Duration `koanf:"validation-poll" reload:"hot"` + PrerecordedBlocks uint64 `koanf:"prerecorded-blocks" reload:"hot"` + ForwardBlocks uint64 `koanf:"forward-blocks" reload:"hot"` + CurrentModuleRoot string `koanf:"current-module-root"` // TODO(magic) requires reinitialization on hot reload + PendingUpgradeModuleRoot string `koanf:"pending-upgrade-module-root"` // TODO(magic) requires StatelessBlockValidator recreation on hot reload + FailureIsFatal bool `koanf:"failure-is-fatal" reload:"hot"` + Dangerous BlockValidatorDangerousConfig `koanf:"dangerous"` + MemoryFreeLimit string `koanf:"memory-free-limit" reload:"hot"` + ValidationServerConfigsList string `koanf:"validation-server-configs-list" reload:"hot"` memoryFreeLimit int } diff --git a/staker/stateless_block_validator.go b/staker/stateless_block_validator.go index abfc08ec33..cfccc793ad 100644 --- a/staker/stateless_block_validator.go +++ b/staker/stateless_block_validator.go @@ -194,11 +194,19 @@ func NewStatelessBlockValidator( config func() *BlockValidatorConfig, stack *node.Node, ) (*StatelessBlockValidator, error) { + validationSpawners := make([]validator.ValidationSpawner, len(config().ValidationServerConfigs)) for i, serverConfig := range config().ValidationServerConfigs { valConfFetcher := func() *rpcclient.ClientConfig { return &serverConfig } validationSpawners[i] = server_api.NewValidationClient(valConfFetcher, stack) } + redisValClient, err := server_api.NewRedisValidationClient(&config().RedisValidationClientConfig) + if err != nil { + log.Error("Creating redis validation client", "error", err) + } else { + validationSpawners = append(validationSpawners, redisValClient) + } + valConfFetcher := func() *rpcclient.ClientConfig { return &config().ValidationServerConfigs[0] } execClient := server_api.NewExecutionClient(valConfFetcher, stack) validator := &StatelessBlockValidator{ diff --git a/validator/server_api/redisconsumer.go b/validator/server_api/redisconsumer.go new file mode 100644 index 0000000000..bba8404ba3 --- /dev/null +++ b/validator/server_api/redisconsumer.go @@ -0,0 +1,64 @@ +package server_api + +import ( + "context" + "fmt" + "time" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/log" + "github.com/offchainlabs/nitro/pubsub" + "github.com/offchainlabs/nitro/util/stopwaiter" + "github.com/offchainlabs/nitro/validator" + "github.com/offchainlabs/nitro/validator/server_api/validation" +) + +// RedisValidationServer implements consumer for the requests originated from +// RedisValidationClient producers. +type RedisValidationServer struct { + stopwaiter.StopWaiter + spawner validator.ValidationSpawner + + // consumers stores moduleRoot to consumer mapping. + consumers map[common.Hash]*pubsub.Consumer[*validator.ValidationInput, validator.GoGlobalState] +} + +func NewRedisValidationServer(cfg *validation.RedisValidationServerConfig) (*RedisValidationServer, error) { + res := &RedisValidationServer{} + for _, mr := range cfg.ModuleRoots { + conf := cfg.ConsumerConfig.Clone() + conf.RedisStream, conf.RedisGroup = redisStreamForRoot(mr), redisGroupForRoot(mr) + c, err := pubsub.NewConsumer[*validator.ValidationInput, validator.GoGlobalState](&conf) + if err != nil { + return nil, fmt.Errorf("creating consumer for validation: %w", err) + } + res.consumers[mr] = c + } + return res, nil +} + +func (s *RedisValidationServer) Start(ctx_in context.Context) { + s.StopWaiter.Start(ctx_in, s) + for moduleRoot, c := range s.consumers { + c := c + c.Start(ctx_in) + s.StopWaiter.CallIteratively(func(ctx context.Context) time.Duration { + req, err := c.Consume(ctx) + if err != nil { + log.Error("Consuming request", "error", err) + return 0 + } + valRun := s.spawner.Launch(req.Value, moduleRoot) + res, err := valRun.Await(ctx) + if err != nil { + log.Error("Error validating", "input", "request value", req.Value, "error", err) + return 0 + } + if err := c.SetResult(ctx, req.ID, res); err != nil { + log.Error("Error setting result for request", "id", req.ID, "result", res, "error", err) + return 0 + } + return time.Second + }) + } +} diff --git a/validator/server_api/redisproducer.go b/validator/server_api/redisproducer.go new file mode 100644 index 0000000000..cda3948421 --- /dev/null +++ b/validator/server_api/redisproducer.go @@ -0,0 +1,116 @@ +package server_api + +import ( + "context" + "fmt" + + "github.com/ethereum/go-ethereum/common" + "github.com/offchainlabs/nitro/pubsub" + "github.com/offchainlabs/nitro/util/containers" + "github.com/offchainlabs/nitro/util/stopwaiter" + "github.com/offchainlabs/nitro/validator" + "github.com/offchainlabs/nitro/validator/server_common" + "github.com/spf13/pflag" +) + +type RedisValidationClientConfig struct { + Name string `koanf:"name"` + Room int32 `koanf:"room"` + ProducerConfig pubsub.ProducerConfig `koanf:"producer-config"` + // Supported wasm module roots. + ModuleRoots []common.Hash `koanf:"module-roots"` +} + +var DefaultRedisValidationClientConfig = &RedisValidationClientConfig{ + Name: "redis validation client", + Room: 2, + ProducerConfig: pubsub.DefaultProducerConfig, +} + +var TestRedisValidationClientConfig = &RedisValidationClientConfig{ + Name: "test redis validation client", + Room: 2, + ProducerConfig: pubsub.TestProducerConfig, +} + +func RedisValidationClientConfigAddOptions(prefix string, f *pflag.FlagSet) { + f.String(prefix+".name", DefaultRedisValidationClientConfig.Name, "validation client name") + f.Uint64(prefix+".room", uint64(DefaultRedisValidationClientConfig.Room), "validation client room") + pubsub.ProducerAddConfigAddOptions(prefix+".producer-config", f) + // TODO(anodar): initialize module roots here. +} + +// RedisValidationClient implements validation client through redis streams. +type RedisValidationClient struct { + stopwaiter.StopWaiter + name string + room int32 + // producers stores moduleRoot to producer mapping. + producers map[common.Hash]*pubsub.Producer[*validator.ValidationInput, validator.GoGlobalState] +} + +func redisGroupForRoot(moduleRoot common.Hash) string { + return fmt.Sprintf("group:%s", moduleRoot.Hex()) +} + +func redisStreamForRoot(moduleRoot common.Hash) string { + return fmt.Sprintf("group:%s", moduleRoot.Hex()) +} + +func NewRedisValidationClient(cfg *RedisValidationClientConfig) (*RedisValidationClient, error) { + res := &RedisValidationClient{ + name: cfg.Name, + room: cfg.Room, + producers: make(map[common.Hash]*pubsub.Producer[*validator.ValidationInput, validator.GoGlobalState]), + } + for _, mr := range cfg.ModuleRoots { + c := cfg.ProducerConfig.Clone() + c.RedisStream, c.RedisGroup = redisGroupForRoot(mr), redisStreamForRoot(mr) + p, err := pubsub.NewProducer[*validator.ValidationInput, validator.GoGlobalState](&c) + if err != nil { + return nil, fmt.Errorf("creating producer for validation: %w", err) + } + res.producers[mr] = p + } + return res, nil +} + +func (c *RedisValidationClient) Launch(entry *validator.ValidationInput, moduleRoot common.Hash) validator.ValidationRun { + producer, found := c.producers[moduleRoot] + if !found { + errPromise := containers.NewReadyPromise(validator.GoGlobalState{}, fmt.Errorf("no validation is configured for wasm root %v", moduleRoot)) + return server_common.NewValRun(errPromise, moduleRoot) + } + promise, err := producer.Produce(c.GetContext(), entry) + if err != nil { + errPromise := containers.NewReadyPromise(validator.GoGlobalState{}, fmt.Errorf("error producing input: %w", err)) + return server_common.NewValRun(errPromise, moduleRoot) + } + return server_common.NewValRun(promise, moduleRoot) +} + +func (c *RedisValidationClient) Start(ctx_in context.Context) error { + for _, p := range c.producers { + p.Start(ctx_in) + } + c.StopWaiter.Start(ctx_in, c) + return nil +} + +func (c *RedisValidationClient) Stop() { + for _, p := range c.producers { + p.StopAndWait() + } + c.StopWaiter.StopAndWait() +} + +func (c *RedisValidationClient) Name() string { + if c.Started() { + return c.name + } + return "(not started)" +} + +func (c *RedisValidationClient) Room() int { + return int(c.room) +} diff --git a/validator/server_api/validation/validation.go b/validator/server_api/validation/validation.go new file mode 100644 index 0000000000..75276f511c --- /dev/null +++ b/validator/server_api/validation/validation.go @@ -0,0 +1,51 @@ +// Package validation is introduced to avoid cyclic depenency between validation +// client and validation api. +package validation + +import ( + "github.com/ethereum/go-ethereum/common" + "github.com/offchainlabs/nitro/arbutil" + "github.com/offchainlabs/nitro/pubsub" + "github.com/offchainlabs/nitro/util/jsonapi" + "github.com/offchainlabs/nitro/validator" + "github.com/spf13/pflag" +) + +type Request struct { + Input *InputJSON + ModuleRoot common.Hash +} + +type InputJSON struct { + Id uint64 + HasDelayedMsg bool + DelayedMsgNr uint64 + PreimagesB64 map[arbutil.PreimageType]*jsonapi.PreimagesMapJson + BatchInfo []BatchInfoJson + DelayedMsgB64 string + StartState validator.GoGlobalState +} + +type BatchInfoJson struct { + Number uint64 + DataB64 string +} + +type RedisValidationServerConfig struct { + ConsumerConfig pubsub.ConsumerConfig `koanf:"consumer-config"` + // Supported wasm module roots. + ModuleRoots []common.Hash `koanf:"module-roots"` +} + +var DefaultRedisValidationServerConfig = &RedisValidationServerConfig{ + ConsumerConfig: *pubsub.DefaultConsumerConfig, +} + +var TestRedisValidationServerConfig = &RedisValidationServerConfig{ + ConsumerConfig: *pubsub.TestConsumerConfig, +} + +func RedisValidationServerConfigAddOptions(prefix string, f *pflag.FlagSet) { + pubsub.ProducerAddConfigAddOptions(prefix+".producer-config", f) + // TODO(anodar): initialize module roots here. +} diff --git a/validator/server_api/validation_api.go b/validator/server_api/validation_api.go index ca5aafcee2..2cdaea931b 100644 --- a/validator/server_api/validation_api.go +++ b/validator/server_api/validation_api.go @@ -9,6 +9,7 @@ import ( "time" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/log" "github.com/offchainlabs/nitro/util/stopwaiter" "github.com/offchainlabs/nitro/validator" @@ -57,15 +58,22 @@ type ExecServerAPI struct { runIdLock sync.Mutex nextId uint64 runs map[uint64]*execRunEntry + + redisConsumer *RedisValidationServer } func NewExecutionServerAPI(valSpawner validator.ValidationSpawner, execution validator.ExecutionSpawner, config server_arb.ArbitratorSpawnerConfigFecher) *ExecServerAPI { + redisConsumer, err := NewRedisValidationServer(&config().RedisValidationServerConfig) + if err != nil { + log.Error("Creating new redis validation server", "error", err) + } return &ExecServerAPI{ ValidationServerAPI: *NewValidationServerAPI(valSpawner), execSpawner: execution, nextId: rand.Uint64(), // good-enough to aver reusing ids after reboot runs: make(map[uint64]*execRunEntry), config: config, + redisConsumer: redisConsumer, } } @@ -105,6 +113,9 @@ func (a *ExecServerAPI) removeOldRuns(ctx context.Context) time.Duration { func (a *ExecServerAPI) Start(ctx_in context.Context) { a.StopWaiter.Start(ctx_in, a) a.CallIteratively(a.removeOldRuns) + if a.redisConsumer != nil { + a.redisConsumer.Start(ctx_in) + } } func (a *ExecServerAPI) WriteToFile(ctx context.Context, jsonInput *ValidationInputJson, expOut validator.GoGlobalState, moduleRoot common.Hash) error { diff --git a/validator/server_arb/validator_spawner.go b/validator/server_arb/validator_spawner.go index 67aa5477eb..9366487793 100644 --- a/validator/server_arb/validator_spawner.go +++ b/validator/server_arb/validator_spawner.go @@ -17,6 +17,7 @@ import ( "github.com/offchainlabs/nitro/util/containers" "github.com/offchainlabs/nitro/util/stopwaiter" "github.com/offchainlabs/nitro/validator" + "github.com/offchainlabs/nitro/validator/server_api/validation" "github.com/offchainlabs/nitro/validator/server_common" "github.com/ethereum/go-ethereum/common" @@ -27,10 +28,11 @@ import ( var arbitratorValidationSteps = metrics.NewRegisteredHistogram("arbitrator/validation/steps", nil, metrics.NewBoundedHistogramSample()) type ArbitratorSpawnerConfig struct { - Workers int `koanf:"workers" reload:"hot"` - OutputPath string `koanf:"output-path" reload:"hot"` - Execution MachineCacheConfig `koanf:"execution" reload:"hot"` // hot reloading for new executions only - ExecutionRunTimeout time.Duration `koanf:"execution-run-timeout" reload:"hot"` + Workers int `koanf:"workers" reload:"hot"` + OutputPath string `koanf:"output-path" reload:"hot"` + Execution MachineCacheConfig `koanf:"execution" reload:"hot"` // hot reloading for new executions only + ExecutionRunTimeout time.Duration `koanf:"execution-run-timeout" reload:"hot"` + RedisValidationServerConfig validation.RedisValidationServerConfig `koanf:"redis-validation-server-config"` } type ArbitratorSpawnerConfigFecher func() *ArbitratorSpawnerConfig @@ -47,6 +49,7 @@ func ArbitratorSpawnerConfigAddOptions(prefix string, f *flag.FlagSet) { f.Duration(prefix+".execution-run-timeout", DefaultArbitratorSpawnerConfig.ExecutionRunTimeout, "timeout before discarding execution run") f.String(prefix+".output-path", DefaultArbitratorSpawnerConfig.OutputPath, "path to write machines to") MachineCacheConfigConfigAddOptions(prefix+".execution", f) + validation.RedisValidationServerConfigAddOptions(prefix+".redis-validation-server-config", f) } func DefaultArbitratorSpawnerConfigFetcher() *ArbitratorSpawnerConfig { From 46749920a122bc8861c8e3b945f2786d8fba3fb3 Mon Sep 17 00:00:00 2001 From: Nodar Ambroladze Date: Fri, 19 Apr 2024 17:53:32 +0200 Subject: [PATCH 02/17] Move RedisValidationServer to ValidationNode --- linters/linters.go | 3 ++- pubsub/consumer.go | 4 ++-- validator/server_api/json.go | 24 ++++--------------- validator/server_api/redisconsumer.go | 3 ++- validator/server_api/redisproducer.go | 16 ++++++++----- validator/server_api/validation/validation.go | 16 +++++++------ validator/server_api/validation_api.go | 18 ++++---------- validator/server_arb/validator_spawner.go | 9 +++---- validator/valnode/valnode.go | 12 +++++++++- 9 files changed, 50 insertions(+), 55 deletions(-) diff --git a/linters/linters.go b/linters/linters.go index bf12b4d7c6..a6c9f6d55e 100644 --- a/linters/linters.go +++ b/linters/linters.go @@ -1,6 +1,7 @@ package main import ( + "github.com/offchainlabs/nitro/linters/koanf" "github.com/offchainlabs/nitro/linters/pointercheck" "github.com/offchainlabs/nitro/linters/rightshift" "github.com/offchainlabs/nitro/linters/structinit" @@ -9,7 +10,7 @@ import ( func main() { multichecker.Main( - // koanf.Analyzer, + koanf.Analyzer, pointercheck.Analyzer, rightshift.Analyzer, structinit.Analyzer, diff --git a/pubsub/consumer.go b/pubsub/consumer.go index e899c458fe..92094edbdc 100644 --- a/pubsub/consumer.go +++ b/pubsub/consumer.go @@ -39,14 +39,14 @@ func (c ConsumerConfig) Clone() ConsumerConfig { } } -var DefaultConsumerConfig = &ConsumerConfig{ +var DefaultConsumerConfig = ConsumerConfig{ ResponseEntryTimeout: time.Hour, KeepAliveTimeout: 5 * time.Minute, RedisStream: "", RedisGroup: "", } -var TestConsumerConfig = &ConsumerConfig{ +var TestConsumerConfig = ConsumerConfig{ RedisStream: "", RedisGroup: "", ResponseEntryTimeout: time.Minute, diff --git a/validator/server_api/json.go b/validator/server_api/json.go index 2029741989..c1e4729571 100644 --- a/validator/server_api/json.go +++ b/validator/server_api/json.go @@ -10,29 +10,15 @@ import ( "github.com/offchainlabs/nitro/arbutil" "github.com/offchainlabs/nitro/util/jsonapi" "github.com/offchainlabs/nitro/validator" + "github.com/offchainlabs/nitro/validator/server_api/validation" ) -type BatchInfoJson struct { - Number uint64 - DataB64 string -} - -type ValidationInputJson struct { - Id uint64 - HasDelayedMsg bool - DelayedMsgNr uint64 - PreimagesB64 map[arbutil.PreimageType]*jsonapi.PreimagesMapJson - BatchInfo []BatchInfoJson - DelayedMsgB64 string - StartState validator.GoGlobalState -} - -func ValidationInputToJson(entry *validator.ValidationInput) *ValidationInputJson { +func ValidationInputToJson(entry *validator.ValidationInput) *validation.InputJSON { jsonPreimagesMap := make(map[arbutil.PreimageType]*jsonapi.PreimagesMapJson) for ty, preimages := range entry.Preimages { jsonPreimagesMap[ty] = jsonapi.NewPreimagesMapJson(preimages) } - res := &ValidationInputJson{ + res := &validation.InputJSON{ Id: entry.Id, HasDelayedMsg: entry.HasDelayedMsg, DelayedMsgNr: entry.DelayedMsgNr, @@ -42,12 +28,12 @@ func ValidationInputToJson(entry *validator.ValidationInput) *ValidationInputJso } for _, binfo := range entry.BatchInfo { encData := base64.StdEncoding.EncodeToString(binfo.Data) - res.BatchInfo = append(res.BatchInfo, BatchInfoJson{binfo.Number, encData}) + res.BatchInfo = append(res.BatchInfo, validation.BatchInfoJson{Number: binfo.Number, DataB64: encData}) } return res } -func ValidationInputFromJson(entry *ValidationInputJson) (*validator.ValidationInput, error) { +func ValidationInputFromJson(entry *validation.InputJSON) (*validator.ValidationInput, error) { preimages := make(map[arbutil.PreimageType]map[common.Hash][]byte) for ty, jsonPreimages := range entry.PreimagesB64 { preimages[ty] = jsonPreimages.Map diff --git a/validator/server_api/redisconsumer.go b/validator/server_api/redisconsumer.go index bba8404ba3..5053020a69 100644 --- a/validator/server_api/redisconsumer.go +++ b/validator/server_api/redisconsumer.go @@ -25,7 +25,8 @@ type RedisValidationServer struct { func NewRedisValidationServer(cfg *validation.RedisValidationServerConfig) (*RedisValidationServer, error) { res := &RedisValidationServer{} - for _, mr := range cfg.ModuleRoots { + for _, hash := range cfg.ModuleRoots { + mr := common.HexToHash(hash) conf := cfg.ConsumerConfig.Clone() conf.RedisStream, conf.RedisGroup = redisStreamForRoot(mr), redisGroupForRoot(mr) c, err := pubsub.NewConsumer[*validator.ValidationInput, validator.GoGlobalState](&conf) diff --git a/validator/server_api/redisproducer.go b/validator/server_api/redisproducer.go index cda3948421..0daab53b07 100644 --- a/validator/server_api/redisproducer.go +++ b/validator/server_api/redisproducer.go @@ -3,6 +3,7 @@ package server_api import ( "context" "fmt" + "sync/atomic" "github.com/ethereum/go-ethereum/common" "github.com/offchainlabs/nitro/pubsub" @@ -18,16 +19,16 @@ type RedisValidationClientConfig struct { Room int32 `koanf:"room"` ProducerConfig pubsub.ProducerConfig `koanf:"producer-config"` // Supported wasm module roots. - ModuleRoots []common.Hash `koanf:"module-roots"` + ModuleRoots []string `koanf:"module-roots"` } -var DefaultRedisValidationClientConfig = &RedisValidationClientConfig{ +var DefaultRedisValidationClientConfig = RedisValidationClientConfig{ Name: "redis validation client", Room: 2, ProducerConfig: pubsub.DefaultProducerConfig, } -var TestRedisValidationClientConfig = &RedisValidationClientConfig{ +var TestRedisValidationClientConfig = RedisValidationClientConfig{ Name: "test redis validation client", Room: 2, ProducerConfig: pubsub.TestProducerConfig, @@ -37,7 +38,7 @@ func RedisValidationClientConfigAddOptions(prefix string, f *pflag.FlagSet) { f.String(prefix+".name", DefaultRedisValidationClientConfig.Name, "validation client name") f.Uint64(prefix+".room", uint64(DefaultRedisValidationClientConfig.Room), "validation client room") pubsub.ProducerAddConfigAddOptions(prefix+".producer-config", f) - // TODO(anodar): initialize module roots here. + f.StringSlice(prefix+".module-roots", nil, "Supported module root hashes") } // RedisValidationClient implements validation client through redis streams. @@ -54,7 +55,7 @@ func redisGroupForRoot(moduleRoot common.Hash) string { } func redisStreamForRoot(moduleRoot common.Hash) string { - return fmt.Sprintf("group:%s", moduleRoot.Hex()) + return fmt.Sprintf("stream:%s", moduleRoot.Hex()) } func NewRedisValidationClient(cfg *RedisValidationClientConfig) (*RedisValidationClient, error) { @@ -63,7 +64,8 @@ func NewRedisValidationClient(cfg *RedisValidationClientConfig) (*RedisValidatio room: cfg.Room, producers: make(map[common.Hash]*pubsub.Producer[*validator.ValidationInput, validator.GoGlobalState]), } - for _, mr := range cfg.ModuleRoots { + for _, hash := range cfg.ModuleRoots { + mr := common.HexToHash(hash) c := cfg.ProducerConfig.Clone() c.RedisStream, c.RedisGroup = redisGroupForRoot(mr), redisStreamForRoot(mr) p, err := pubsub.NewProducer[*validator.ValidationInput, validator.GoGlobalState](&c) @@ -76,6 +78,8 @@ func NewRedisValidationClient(cfg *RedisValidationClientConfig) (*RedisValidatio } func (c *RedisValidationClient) Launch(entry *validator.ValidationInput, moduleRoot common.Hash) validator.ValidationRun { + atomic.AddInt32(&c.room, -1) + defer atomic.AddInt32(&c.room, 1) producer, found := c.producers[moduleRoot] if !found { errPromise := containers.NewReadyPromise(validator.GoGlobalState{}, fmt.Errorf("no validation is configured for wasm root %v", moduleRoot)) diff --git a/validator/server_api/validation/validation.go b/validator/server_api/validation/validation.go index 75276f511c..324de2d10b 100644 --- a/validator/server_api/validation/validation.go +++ b/validator/server_api/validation/validation.go @@ -34,18 +34,20 @@ type BatchInfoJson struct { type RedisValidationServerConfig struct { ConsumerConfig pubsub.ConsumerConfig `koanf:"consumer-config"` // Supported wasm module roots. - ModuleRoots []common.Hash `koanf:"module-roots"` + ModuleRoots []string `koanf:"module-roots"` } -var DefaultRedisValidationServerConfig = &RedisValidationServerConfig{ - ConsumerConfig: *pubsub.DefaultConsumerConfig, +var DefaultRedisValidationServerConfig = RedisValidationServerConfig{ + ConsumerConfig: pubsub.DefaultConsumerConfig, + ModuleRoots: []string{}, } -var TestRedisValidationServerConfig = &RedisValidationServerConfig{ - ConsumerConfig: *pubsub.TestConsumerConfig, +var TestRedisValidationServerConfig = RedisValidationServerConfig{ + ConsumerConfig: pubsub.TestConsumerConfig, + ModuleRoots: []string{}, } func RedisValidationServerConfigAddOptions(prefix string, f *pflag.FlagSet) { - pubsub.ProducerAddConfigAddOptions(prefix+".producer-config", f) - // TODO(anodar): initialize module roots here. + pubsub.ConsumerConfigAddOptions(prefix+".consumer-config", f) + f.StringSlice(prefix+".module-roots", nil, "Supported module root hashes") } diff --git a/validator/server_api/validation_api.go b/validator/server_api/validation_api.go index 2cdaea931b..076e1ef79c 100644 --- a/validator/server_api/validation_api.go +++ b/validator/server_api/validation_api.go @@ -9,10 +9,10 @@ import ( "time" "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/log" "github.com/offchainlabs/nitro/util/stopwaiter" "github.com/offchainlabs/nitro/validator" + "github.com/offchainlabs/nitro/validator/server_api/validation" "github.com/offchainlabs/nitro/validator/server_arb" ) @@ -30,7 +30,7 @@ func (a *ValidationServerAPI) Room() int { return a.spawner.Room() } -func (a *ValidationServerAPI) Validate(ctx context.Context, entry *ValidationInputJson, moduleRoot common.Hash) (validator.GoGlobalState, error) { +func (a *ValidationServerAPI) Validate(ctx context.Context, entry *validation.InputJSON, moduleRoot common.Hash) (validator.GoGlobalState, error) { valInput, err := ValidationInputFromJson(entry) if err != nil { return validator.GoGlobalState{}, err @@ -58,26 +58,19 @@ type ExecServerAPI struct { runIdLock sync.Mutex nextId uint64 runs map[uint64]*execRunEntry - - redisConsumer *RedisValidationServer } func NewExecutionServerAPI(valSpawner validator.ValidationSpawner, execution validator.ExecutionSpawner, config server_arb.ArbitratorSpawnerConfigFecher) *ExecServerAPI { - redisConsumer, err := NewRedisValidationServer(&config().RedisValidationServerConfig) - if err != nil { - log.Error("Creating new redis validation server", "error", err) - } return &ExecServerAPI{ ValidationServerAPI: *NewValidationServerAPI(valSpawner), execSpawner: execution, nextId: rand.Uint64(), // good-enough to aver reusing ids after reboot runs: make(map[uint64]*execRunEntry), config: config, - redisConsumer: redisConsumer, } } -func (a *ExecServerAPI) CreateExecutionRun(ctx context.Context, wasmModuleRoot common.Hash, jsonInput *ValidationInputJson) (uint64, error) { +func (a *ExecServerAPI) CreateExecutionRun(ctx context.Context, wasmModuleRoot common.Hash, jsonInput *validation.InputJSON) (uint64, error) { input, err := ValidationInputFromJson(jsonInput) if err != nil { return 0, err @@ -113,12 +106,9 @@ func (a *ExecServerAPI) removeOldRuns(ctx context.Context) time.Duration { func (a *ExecServerAPI) Start(ctx_in context.Context) { a.StopWaiter.Start(ctx_in, a) a.CallIteratively(a.removeOldRuns) - if a.redisConsumer != nil { - a.redisConsumer.Start(ctx_in) - } } -func (a *ExecServerAPI) WriteToFile(ctx context.Context, jsonInput *ValidationInputJson, expOut validator.GoGlobalState, moduleRoot common.Hash) error { +func (a *ExecServerAPI) WriteToFile(ctx context.Context, jsonInput *validation.InputJSON, expOut validator.GoGlobalState, moduleRoot common.Hash) error { input, err := ValidationInputFromJson(jsonInput) if err != nil { return err diff --git a/validator/server_arb/validator_spawner.go b/validator/server_arb/validator_spawner.go index 9366487793..a20a8d0e27 100644 --- a/validator/server_arb/validator_spawner.go +++ b/validator/server_arb/validator_spawner.go @@ -38,10 +38,11 @@ type ArbitratorSpawnerConfig struct { type ArbitratorSpawnerConfigFecher func() *ArbitratorSpawnerConfig var DefaultArbitratorSpawnerConfig = ArbitratorSpawnerConfig{ - Workers: 0, - OutputPath: "./target/output", - Execution: DefaultMachineCacheConfig, - ExecutionRunTimeout: time.Minute * 15, + Workers: 0, + OutputPath: "./target/output", + Execution: DefaultMachineCacheConfig, + ExecutionRunTimeout: time.Minute * 15, + RedisValidationServerConfig: validation.DefaultRedisValidationServerConfig, } func ArbitratorSpawnerConfigAddOptions(prefix string, f *flag.FlagSet) { diff --git a/validator/valnode/valnode.go b/validator/valnode/valnode.go index ca954094ff..5b4986f9da 100644 --- a/validator/valnode/valnode.go +++ b/validator/valnode/valnode.go @@ -5,6 +5,7 @@ import ( "github.com/offchainlabs/nitro/validator" + "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/node" "github.com/ethereum/go-ethereum/rpc" flag "github.com/spf13/pflag" @@ -75,6 +76,8 @@ type ValidationNode struct { config ValidationConfigFetcher arbSpawner *server_arb.ArbitratorSpawner jitSpawner *server_jit.JitSpawner + + redisConsumer *server_api.RedisValidationServer } func EnsureValidationExposedViaAuthRPC(stackConf *node.Config) { @@ -116,6 +119,10 @@ func CreateValidationNode(configFetcher ValidationConfigFetcher, stack *node.Nod } else { serverAPI = server_api.NewExecutionServerAPI(arbSpawner, arbSpawner, arbConfigFetcher) } + redisConsumer, err := server_api.NewRedisValidationServer(&arbConfigFetcher().RedisValidationServerConfig) + if err != nil { + log.Error("Creating new redis validation server", "error", err) + } valAPIs := []rpc.API{{ Namespace: server_api.Namespace, Version: "1.0", @@ -125,7 +132,7 @@ func CreateValidationNode(configFetcher ValidationConfigFetcher, stack *node.Nod }} stack.RegisterAPIs(valAPIs) - return &ValidationNode{configFetcher, arbSpawner, jitSpawner}, nil + return &ValidationNode{configFetcher, arbSpawner, jitSpawner, redisConsumer}, nil } func (v *ValidationNode) Start(ctx context.Context) error { @@ -137,6 +144,9 @@ func (v *ValidationNode) Start(ctx context.Context) error { return err } } + if v.redisConsumer != nil { + v.redisConsumer.Start(ctx) + } return nil } From 51d4666b8ae86123e9d671c6ac614fb96945499b Mon Sep 17 00:00:00 2001 From: Nodar Ambroladze Date: Fri, 19 Apr 2024 18:55:09 +0200 Subject: [PATCH 03/17] Move redisURL and redisStream out from producer and consumer, pass it to the constructor instead --- pubsub/consumer.go | 53 +++++---------- pubsub/producer.go | 65 +++++++------------ pubsub/pubsub_test.go | 28 ++++---- validator/server_api/redisconsumer.go | 20 ++++-- validator/server_api/redisproducer.go | 26 +++++--- validator/server_api/validation/validation.go | 6 ++ 6 files changed, 90 insertions(+), 108 deletions(-) diff --git a/pubsub/consumer.go b/pubsub/consumer.go index 92094edbdc..7f8ca3a988 100644 --- a/pubsub/consumer.go +++ b/pubsub/consumer.go @@ -10,7 +10,6 @@ import ( "github.com/ethereum/go-ethereum/log" "github.com/go-redis/redis/v8" "github.com/google/uuid" - "github.com/offchainlabs/nitro/util/redisutil" "github.com/offchainlabs/nitro/util/stopwaiter" "github.com/spf13/pflag" ) @@ -21,34 +20,21 @@ type ConsumerConfig struct { // Duration after which consumer is considered to be dead if heartbeat // is not updated. KeepAliveTimeout time.Duration `koanf:"keepalive-timeout"` - // Redis url for Redis streams and locks. - RedisURL string `koanf:"redis-url"` - // Redis stream name. - RedisStream string `koanf:"redis-stream"` - // Redis consumer group name. - RedisGroup string `koanf:"redis-group"` } func (c ConsumerConfig) Clone() ConsumerConfig { return ConsumerConfig{ ResponseEntryTimeout: c.ResponseEntryTimeout, KeepAliveTimeout: c.KeepAliveTimeout, - RedisURL: c.RedisURL, - RedisStream: c.RedisStream, - RedisGroup: c.RedisGroup, } } var DefaultConsumerConfig = ConsumerConfig{ ResponseEntryTimeout: time.Hour, KeepAliveTimeout: 5 * time.Minute, - RedisStream: "", - RedisGroup: "", } var TestConsumerConfig = ConsumerConfig{ - RedisStream: "", - RedisGroup: "", ResponseEntryTimeout: time.Minute, KeepAliveTimeout: 30 * time.Millisecond, } @@ -56,18 +42,17 @@ var TestConsumerConfig = ConsumerConfig{ func ConsumerConfigAddOptions(prefix string, f *pflag.FlagSet) { f.Duration(prefix+".response-entry-timeout", DefaultConsumerConfig.ResponseEntryTimeout, "timeout for response entry") f.Duration(prefix+".keepalive-timeout", DefaultConsumerConfig.KeepAliveTimeout, "timeout after which consumer is considered inactive if heartbeat wasn't performed") - f.String(prefix+".redis-url", DefaultConsumerConfig.RedisURL, "redis url for redis stream") - f.String(prefix+".redis-stream", DefaultConsumerConfig.RedisStream, "redis stream name to read from") - f.String(prefix+".redis-group", DefaultConsumerConfig.RedisGroup, "redis stream consumer group name") } // Consumer implements a consumer for redis stream provides heartbeat to // indicate it is alive. type Consumer[Request any, Response any] struct { stopwaiter.StopWaiter - id string - client redis.UniversalClient - cfg *ConsumerConfig + id string + client redis.UniversalClient + redisStream string + redisGroup string + cfg *ConsumerConfig } type Message[Request any] struct { @@ -75,24 +60,16 @@ type Message[Request any] struct { Value Request } -func NewConsumer[Request any, Response any](cfg *ConsumerConfig) (*Consumer[Request, Response], error) { - if cfg.RedisURL == "" { - return nil, fmt.Errorf("redis url cannot be empty") - } - if cfg.RedisStream == "" { +func NewConsumer[Request any, Response any](client redis.UniversalClient, streamName string, cfg *ConsumerConfig) (*Consumer[Request, Response], error) { + if streamName == "" { return nil, fmt.Errorf("redis stream name cannot be empty") } - if cfg.RedisGroup == "" { - return nil, fmt.Errorf("redis group name cannot be emtpy") - } - c, err := redisutil.RedisClientFromURL(cfg.RedisURL) - if err != nil { - return nil, err - } consumer := &Consumer[Request, Response]{ - id: uuid.NewString(), - client: c, - cfg: cfg, + id: uuid.NewString(), + client: client, + redisStream: streamName, + redisGroup: streamName, // There is 1-1 mapping of redis stream and consumer group. + cfg: cfg, } return consumer, nil } @@ -135,11 +112,11 @@ func (c *Consumer[Request, Response]) heartBeat(ctx context.Context) { // unresponsive consumer, if not then reads from the stream. func (c *Consumer[Request, Response]) Consume(ctx context.Context) (*Message[Request], error) { res, err := c.client.XReadGroup(ctx, &redis.XReadGroupArgs{ - Group: c.cfg.RedisGroup, + Group: c.redisGroup, Consumer: c.id, // Receive only messages that were never delivered to any other consumer, // that is, only new messages. - Streams: []string{c.cfg.RedisStream, ">"}, + Streams: []string{c.redisStream, ">"}, Count: 1, Block: time.Millisecond, // 0 seems to block the read instead of immediately returning }).Result() @@ -180,7 +157,7 @@ func (c *Consumer[Request, Response]) SetResult(ctx context.Context, messageID s if err != nil || !acquired { return fmt.Errorf("setting result for message: %v, error: %w", messageID, err) } - if _, err := c.client.XAck(ctx, c.cfg.RedisStream, c.cfg.RedisGroup, messageID).Result(); err != nil { + if _, err := c.client.XAck(ctx, c.redisStream, c.redisGroup, messageID).Result(); err != nil { return fmt.Errorf("acking message: %v, error: %w", messageID, err) } return nil diff --git a/pubsub/producer.go b/pubsub/producer.go index a0353c717e..7f7f05389b 100644 --- a/pubsub/producer.go +++ b/pubsub/producer.go @@ -20,7 +20,6 @@ import ( "github.com/go-redis/redis/v8" "github.com/google/uuid" "github.com/offchainlabs/nitro/util/containers" - "github.com/offchainlabs/nitro/util/redisutil" "github.com/offchainlabs/nitro/util/stopwaiter" "github.com/spf13/pflag" ) @@ -32,9 +31,11 @@ const ( type Producer[Request any, Response any] struct { stopwaiter.StopWaiter - id string - client redis.UniversalClient - cfg *ProducerConfig + id string + client redis.UniversalClient + redisStream string + redisGroup string + cfg *ProducerConfig promisesLock sync.RWMutex promises map[string]*containers.Promise[Response] @@ -49,10 +50,7 @@ type ProducerConfig struct { // When enabled, messages that are sent to consumers that later die before // processing them, will be re-inserted into the stream to be proceesed by // another consumer - EnableReproduce bool `koanf:"enable-reproduce"` - RedisURL string `koanf:"redis-url"` - // Redis stream name. - RedisStream string `koanf:"redis-stream"` + EnableReproduce bool `koanf:"enable-reproduce"` // Interval duration in which producer checks for pending messages delivered // to the consumers that are currently inactive. CheckPendingInterval time.Duration `koanf:"check-pending-interval"` @@ -61,26 +59,19 @@ type ProducerConfig struct { KeepAliveTimeout time.Duration `koanf:"keepalive-timeout"` // Interval duration for checking the result set by consumers. CheckResultInterval time.Duration `koanf:"check-result-interval"` - // Redis consumer group name. - RedisGroup string `koanf:"redis-group"` } func (c ProducerConfig) Clone() ProducerConfig { return ProducerConfig{ EnableReproduce: c.EnableReproduce, - RedisURL: c.RedisURL, - RedisStream: c.RedisStream, CheckPendingInterval: c.CheckPendingInterval, KeepAliveTimeout: c.KeepAliveTimeout, CheckResultInterval: c.CheckResultInterval, - RedisGroup: c.RedisGroup, } } var DefaultProducerConfig = ProducerConfig{ EnableReproduce: true, - RedisStream: "", - RedisGroup: "", CheckPendingInterval: time.Second, KeepAliveTimeout: 5 * time.Minute, CheckResultInterval: 5 * time.Second, @@ -88,8 +79,6 @@ var DefaultProducerConfig = ProducerConfig{ var TestProducerConfig = ProducerConfig{ EnableReproduce: true, - RedisStream: "", - RedisGroup: "", CheckPendingInterval: 10 * time.Millisecond, KeepAliveTimeout: 100 * time.Millisecond, CheckResultInterval: 5 * time.Millisecond, @@ -97,32 +86,24 @@ var TestProducerConfig = ProducerConfig{ func ProducerAddConfigAddOptions(prefix string, f *pflag.FlagSet) { f.Bool(prefix+".enable-reproduce", DefaultProducerConfig.EnableReproduce, "when enabled, messages with dead consumer will be re-inserted into the stream") - f.String(prefix+".redis-url", DefaultProducerConfig.RedisURL, "redis url for redis stream") f.Duration(prefix+".check-pending-interval", DefaultProducerConfig.CheckPendingInterval, "interval in which producer checks pending messages whether consumer processing them is inactive") f.Duration(prefix+".keepalive-timeout", DefaultProducerConfig.KeepAliveTimeout, "timeout after which consumer is considered inactive if heartbeat wasn't performed") - f.String(prefix+".redis-stream", DefaultProducerConfig.RedisStream, "redis stream name to read from") - f.String(prefix+".redis-group", DefaultProducerConfig.RedisGroup, "redis stream consumer group name") } -func NewProducer[Request any, Response any](cfg *ProducerConfig) (*Producer[Request, Response], error) { - if cfg.RedisURL == "" { - return nil, fmt.Errorf("redis url cannot be empty") +func NewProducer[Request any, Response any](client redis.UniversalClient, streamName string, cfg *ProducerConfig) (*Producer[Request, Response], error) { + if client == nil { + return nil, fmt.Errorf("redis client cannot be nil") } - if cfg.RedisStream == "" { - return nil, fmt.Errorf("redis stream cannot be emtpy") - } - if cfg.RedisGroup == "" { - return nil, fmt.Errorf("redis group cannot be empty") - } - c, err := redisutil.RedisClientFromURL(cfg.RedisURL) - if err != nil { - return nil, err + if streamName == "" { + return nil, fmt.Errorf("stream name cannot be empty") } return &Producer[Request, Response]{ - id: uuid.NewString(), - client: c, - cfg: cfg, - promises: make(map[string]*containers.Promise[Response]), + id: uuid.NewString(), + client: client, + redisStream: streamName, + redisGroup: streamName, // There is 1-1 mapping of redis stream and consumer group. + cfg: cfg, + promises: make(map[string]*containers.Promise[Response]), }, nil } @@ -154,7 +135,7 @@ func (p *Producer[Request, Response]) checkAndReproduce(ctx context.Context) tim } acked := make(map[string]Request) for _, msg := range msgs { - if _, err := p.client.XAck(ctx, p.cfg.RedisStream, p.cfg.RedisGroup, msg.ID).Result(); err != nil { + if _, err := p.client.XAck(ctx, p.redisStream, p.redisGroup, msg.ID).Result(); err != nil { log.Error("ACKing message", "error", err) continue } @@ -212,7 +193,7 @@ func (p *Producer[Request, Response]) reproduce(ctx context.Context, value Reque return nil, fmt.Errorf("marshaling value: %w", err) } id, err := p.client.XAdd(ctx, &redis.XAddArgs{ - Stream: p.cfg.RedisStream, + Stream: p.redisStream, Values: map[string]any{messageKey: val}, }).Result() if err != nil { @@ -260,8 +241,8 @@ func (p *Producer[Request, Response]) havePromiseFor(messageID string) bool { func (p *Producer[Request, Response]) checkPending(ctx context.Context) ([]*Message[Request], error) { pendingMessages, err := p.client.XPendingExt(ctx, &redis.XPendingExtArgs{ - Stream: p.cfg.RedisStream, - Group: p.cfg.RedisGroup, + Stream: p.redisStream, + Group: p.redisGroup, Start: "-", End: "+", Count: 100, @@ -297,8 +278,8 @@ func (p *Producer[Request, Response]) checkPending(ctx context.Context) ([]*Mess } log.Info("Attempting to claim", "messages", ids) claimedMsgs, err := p.client.XClaim(ctx, &redis.XClaimArgs{ - Stream: p.cfg.RedisStream, - Group: p.cfg.RedisGroup, + Stream: p.redisStream, + Group: p.redisGroup, Consumer: p.id, MinIdle: p.cfg.KeepAliveTimeout, Messages: ids, diff --git a/pubsub/pubsub_test.go b/pubsub/pubsub_test.go index b574c1a68d..949e532343 100644 --- a/pubsub/pubsub_test.go +++ b/pubsub/pubsub_test.go @@ -28,16 +28,17 @@ type testResponse struct { Response string } -func createGroup(ctx context.Context, t *testing.T, streamName, groupName string, client redis.UniversalClient) { +func createGroup(ctx context.Context, t *testing.T, streamName string, client redis.UniversalClient) { t.Helper() - if _, err := client.XGroupCreateMkStream(ctx, streamName, groupName, "$").Result(); err != nil { + // Stream name and group name are the same. + if _, err := client.XGroupCreateMkStream(ctx, streamName, streamName, "$").Result(); err != nil { t.Fatalf("Error creating stream group: %v", err) } } -func destroyGroup(ctx context.Context, t *testing.T, streamName, groupName string, client redis.UniversalClient) { +func destroyGroup(ctx context.Context, t *testing.T, streamName string, client redis.UniversalClient) { t.Helper() - if _, err := client.XGroupDestroy(ctx, streamName, groupName).Result(); err != nil { + if _, err := client.XGroupDestroy(ctx, streamName, streamName).Result(); err != nil { log.Debug("Error destroying a stream group", "error", err) } } @@ -70,33 +71,32 @@ func consumerCfg() *ConsumerConfig { func newProducerConsumers(ctx context.Context, t *testing.T, opts ...configOpt) (*Producer[testRequest, testResponse], []*Consumer[testRequest, testResponse]) { t.Helper() - redisURL := redisutil.CreateTestRedis(ctx, t) + redisClient, err := redisutil.RedisClientFromURL(redisutil.CreateTestRedis(ctx, t)) + if err != nil { + t.Fatalf("RedisClientFromURL() unexpected error: %v", err) + } prodCfg, consCfg := producerCfg(), consumerCfg() - prodCfg.RedisURL, consCfg.RedisURL = redisURL, redisURL - streamName := uuid.NewString() - groupName := fmt.Sprintf("group_%s", streamName) - prodCfg.RedisGroup, consCfg.RedisGroup = groupName, groupName - prodCfg.RedisStream, consCfg.RedisStream = streamName, streamName + streamName := fmt.Sprintf("stream:%s", uuid.NewString()) for _, o := range opts { o.apply(consCfg, prodCfg) } - producer, err := NewProducer[testRequest, testResponse](prodCfg) + producer, err := NewProducer[testRequest, testResponse](redisClient, streamName, prodCfg) if err != nil { t.Fatalf("Error creating new producer: %v", err) } var consumers []*Consumer[testRequest, testResponse] for i := 0; i < consumersCount; i++ { - c, err := NewConsumer[testRequest, testResponse](consCfg) + c, err := NewConsumer[testRequest, testResponse](redisClient, streamName, consCfg) if err != nil { t.Fatalf("Error creating new consumer: %v", err) } consumers = append(consumers, c) } - createGroup(ctx, t, streamName, groupName, producer.client) + createGroup(ctx, t, streamName, producer.client) t.Cleanup(func() { ctx := context.Background() - destroyGroup(ctx, t, streamName, groupName, producer.client) + destroyGroup(ctx, t, streamName, producer.client) var keys []string for _, c := range consumers { keys = append(keys, c.heartBeatKey()) diff --git a/validator/server_api/redisconsumer.go b/validator/server_api/redisconsumer.go index 5053020a69..bc40d19d73 100644 --- a/validator/server_api/redisconsumer.go +++ b/validator/server_api/redisconsumer.go @@ -8,6 +8,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/log" "github.com/offchainlabs/nitro/pubsub" + "github.com/offchainlabs/nitro/util/redisutil" "github.com/offchainlabs/nitro/util/stopwaiter" "github.com/offchainlabs/nitro/validator" "github.com/offchainlabs/nitro/validator/server_api/validation" @@ -24,18 +25,25 @@ type RedisValidationServer struct { } func NewRedisValidationServer(cfg *validation.RedisValidationServerConfig) (*RedisValidationServer, error) { - res := &RedisValidationServer{} + if cfg.RedisURL == "" { + return nil, fmt.Errorf("redis url cannot be empty") + } + redisClient, err := redisutil.RedisClientFromURL(cfg.RedisURL) + if err != nil { + return nil, err + } + consumers := make(map[common.Hash]*pubsub.Consumer[*validator.ValidationInput, validator.GoGlobalState]) for _, hash := range cfg.ModuleRoots { mr := common.HexToHash(hash) - conf := cfg.ConsumerConfig.Clone() - conf.RedisStream, conf.RedisGroup = redisStreamForRoot(mr), redisGroupForRoot(mr) - c, err := pubsub.NewConsumer[*validator.ValidationInput, validator.GoGlobalState](&conf) + c, err := pubsub.NewConsumer[*validator.ValidationInput, validator.GoGlobalState](redisClient, redisStreamForRoot(mr), &cfg.ConsumerConfig) if err != nil { return nil, fmt.Errorf("creating consumer for validation: %w", err) } - res.consumers[mr] = c + consumers[mr] = c } - return res, nil + return &RedisValidationServer{ + consumers: consumers, + }, nil } func (s *RedisValidationServer) Start(ctx_in context.Context) { diff --git a/validator/server_api/redisproducer.go b/validator/server_api/redisproducer.go index 0daab53b07..5540cd169f 100644 --- a/validator/server_api/redisproducer.go +++ b/validator/server_api/redisproducer.go @@ -8,6 +8,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/offchainlabs/nitro/pubsub" "github.com/offchainlabs/nitro/util/containers" + "github.com/offchainlabs/nitro/util/redisutil" "github.com/offchainlabs/nitro/util/stopwaiter" "github.com/offchainlabs/nitro/validator" "github.com/offchainlabs/nitro/validator/server_common" @@ -17,6 +18,8 @@ import ( type RedisValidationClientConfig struct { Name string `koanf:"name"` Room int32 `koanf:"room"` + RedisURL string `koanf:"redis-url"` + RedisStream string `koanf:"redis-stream"` ProducerConfig pubsub.ProducerConfig `koanf:"producer-config"` // Supported wasm module roots. ModuleRoots []string `koanf:"module-roots"` @@ -25,18 +28,23 @@ type RedisValidationClientConfig struct { var DefaultRedisValidationClientConfig = RedisValidationClientConfig{ Name: "redis validation client", Room: 2, + RedisURL: "", + RedisStream: "", ProducerConfig: pubsub.DefaultProducerConfig, } var TestRedisValidationClientConfig = RedisValidationClientConfig{ Name: "test redis validation client", Room: 2, + RedisURL: "", + RedisStream: "", ProducerConfig: pubsub.TestProducerConfig, } func RedisValidationClientConfigAddOptions(prefix string, f *pflag.FlagSet) { f.String(prefix+".name", DefaultRedisValidationClientConfig.Name, "validation client name") - f.Uint64(prefix+".room", uint64(DefaultRedisValidationClientConfig.Room), "validation client room") + f.Int32(prefix+".room", DefaultRedisValidationClientConfig.Room, "validation client room") + f.String(prefix+".redis-stream", DefaultRedisValidationClientConfig.RedisStream, "redis stream name") pubsub.ProducerAddConfigAddOptions(prefix+".producer-config", f) f.StringSlice(prefix+".module-roots", nil, "Supported module root hashes") } @@ -50,10 +58,6 @@ type RedisValidationClient struct { producers map[common.Hash]*pubsub.Producer[*validator.ValidationInput, validator.GoGlobalState] } -func redisGroupForRoot(moduleRoot common.Hash) string { - return fmt.Sprintf("group:%s", moduleRoot.Hex()) -} - func redisStreamForRoot(moduleRoot common.Hash) string { return fmt.Sprintf("stream:%s", moduleRoot.Hex()) } @@ -64,11 +68,17 @@ func NewRedisValidationClient(cfg *RedisValidationClientConfig) (*RedisValidatio room: cfg.Room, producers: make(map[common.Hash]*pubsub.Producer[*validator.ValidationInput, validator.GoGlobalState]), } + if cfg.RedisURL == "" { + return nil, fmt.Errorf("redis url cannot be empty") + } + redisClient, err := redisutil.RedisClientFromURL(cfg.RedisURL) + if err != nil { + return nil, err + } for _, hash := range cfg.ModuleRoots { mr := common.HexToHash(hash) - c := cfg.ProducerConfig.Clone() - c.RedisStream, c.RedisGroup = redisGroupForRoot(mr), redisStreamForRoot(mr) - p, err := pubsub.NewProducer[*validator.ValidationInput, validator.GoGlobalState](&c) + p, err := pubsub.NewProducer[*validator.ValidationInput, validator.GoGlobalState]( + redisClient, redisStreamForRoot(mr), &cfg.ProducerConfig) if err != nil { return nil, fmt.Errorf("creating producer for validation: %w", err) } diff --git a/validator/server_api/validation/validation.go b/validator/server_api/validation/validation.go index 324de2d10b..9cab29bde7 100644 --- a/validator/server_api/validation/validation.go +++ b/validator/server_api/validation/validation.go @@ -32,17 +32,23 @@ type BatchInfoJson struct { } type RedisValidationServerConfig struct { + RedisURL string `koanf:"redis-url"` + RedisStream string `koanf:"redis-stream"` ConsumerConfig pubsub.ConsumerConfig `koanf:"consumer-config"` // Supported wasm module roots. ModuleRoots []string `koanf:"module-roots"` } var DefaultRedisValidationServerConfig = RedisValidationServerConfig{ + RedisURL: "", + RedisStream: "", ConsumerConfig: pubsub.DefaultConsumerConfig, ModuleRoots: []string{}, } var TestRedisValidationServerConfig = RedisValidationServerConfig{ + RedisURL: "", + RedisStream: "", ConsumerConfig: pubsub.TestConsumerConfig, ModuleRoots: []string{}, } From 849667989ff601832a558b2493092a7fab76db06 Mon Sep 17 00:00:00 2001 From: Nodar Ambroladze Date: Fri, 19 Apr 2024 22:39:09 +0200 Subject: [PATCH 04/17] Implement system tests --- arbnode/node.go | 2 +- pubsub/consumer.go | 3 +- pubsub/producer.go | 1 + staker/block_validator.go | 8 +++- staker/stateless_block_validator.go | 34 ++++++++------ system_tests/block_validator_test.go | 31 +++++++++--- system_tests/common_test.go | 47 +++++++++++++++++-- validator/server_api/redisconsumer.go | 9 +++- validator/server_api/redisproducer.go | 13 +++-- validator/server_api/validation/validation.go | 3 -- validator/server_api/validation_client.go | 13 +++-- validator/valnode/valnode.go | 2 +- 12 files changed, 120 insertions(+), 46 deletions(-) diff --git a/arbnode/node.go b/arbnode/node.go index 7a7a99ba88..43a05155fe 100644 --- a/arbnode/node.go +++ b/arbnode/node.go @@ -540,7 +540,7 @@ func createNodeImpl( txStreamer.SetInboxReaders(inboxReader, delayedBridge) var statelessBlockValidator *staker.StatelessBlockValidator - if config.BlockValidator.ValidationServerConfigs[0].URL != "" { + if config.BlockValidator.RedisValidationClientConfig.Enabled() || config.BlockValidator.ValidationServerConfigs[0].URL != "" { statelessBlockValidator, err = staker.NewStatelessBlockValidator( inboxReader, inboxTracker, diff --git a/pubsub/consumer.go b/pubsub/consumer.go index 7f8ca3a988..5385b33979 100644 --- a/pubsub/consumer.go +++ b/pubsub/consumer.go @@ -129,7 +129,6 @@ func (c *Consumer[Request, Response]) Consume(ctx context.Context) (*Message[Req if len(res) != 1 || len(res[0].Messages) != 1 { return nil, fmt.Errorf("redis returned entries: %+v, for querying single message", res) } - log.Debug(fmt.Sprintf("Consumer: %s consuming message: %s", c.id, res[0].Messages[0].ID)) var ( value = res[0].Messages[0].Values[messageKey] data, ok = (value).(string) @@ -141,7 +140,7 @@ func (c *Consumer[Request, Response]) Consume(ctx context.Context) (*Message[Req if err := json.Unmarshal([]byte(data), &req); err != nil { return nil, fmt.Errorf("unmarshaling value: %v, error: %w", value, err) } - + log.Debug("Redis stream consuming", "consumer_id", c.id, "message_id", res[0].Messages[0].ID) return &Message[Request]{ ID: res[0].Messages[0].ID, Value: req, diff --git a/pubsub/producer.go b/pubsub/producer.go index 7f7f05389b..debea81365 100644 --- a/pubsub/producer.go +++ b/pubsub/producer.go @@ -217,6 +217,7 @@ func (p *Producer[Request, Response]) reproduce(ctx context.Context, value Reque } func (p *Producer[Request, Response]) Produce(ctx context.Context, value Request) (*containers.Promise[Response], error) { + log.Debug("Redis stream producing", "value", value) p.once.Do(func() { p.StopWaiter.CallIteratively(p.checkAndReproduce) p.StopWaiter.CallIteratively(p.checkResponses) diff --git a/staker/block_validator.go b/staker/block_validator.go index a65adbeff0..1ec160c558 100644 --- a/staker/block_validator.go +++ b/staker/block_validator.go @@ -111,18 +111,19 @@ func (c *BlockValidatorConfig) Validate() error { } c.memoryFreeLimit = limit } + streamsEnabled := c.RedisValidationClientConfig.Enabled() if c.ValidationServerConfigs == nil { if c.ValidationServerConfigsList == "default" { c.ValidationServerConfigs = []rpcclient.ClientConfig{c.ValidationServer} } else { var validationServersConfigs []rpcclient.ClientConfig - if err := json.Unmarshal([]byte(c.ValidationServerConfigsList), &validationServersConfigs); err != nil { + if err := json.Unmarshal([]byte(c.ValidationServerConfigsList), &validationServersConfigs); err != nil && !streamsEnabled { return fmt.Errorf("failed to parse block-validator validation-server-configs-list string: %w", err) } c.ValidationServerConfigs = validationServersConfigs } } - if len(c.ValidationServerConfigs) == 0 { + if len(c.ValidationServerConfigs) == 0 && !streamsEnabled { return fmt.Errorf("block-validator validation-server-configs is empty, need at least one validation server config") } for _, serverConfig := range c.ValidationServerConfigs { @@ -1032,6 +1033,9 @@ func (v *BlockValidator) Reorg(ctx context.Context, count arbutil.MessageIndex) // Initialize must be called after SetCurrentWasmModuleRoot sets the current one func (v *BlockValidator) Initialize(ctx context.Context) error { config := v.config() + if config.RedisValidationClientConfig.Enabled() && v.execSpawner == nil { + return nil + } currentModuleRoot := config.CurrentModuleRoot switch currentModuleRoot { case "latest": diff --git a/staker/stateless_block_validator.go b/staker/stateless_block_validator.go index cfccc793ad..25d64fae35 100644 --- a/staker/stateless_block_validator.go +++ b/staker/stateless_block_validator.go @@ -194,24 +194,20 @@ func NewStatelessBlockValidator( config func() *BlockValidatorConfig, stack *node.Node, ) (*StatelessBlockValidator, error) { - - validationSpawners := make([]validator.ValidationSpawner, len(config().ValidationServerConfigs)) - for i, serverConfig := range config().ValidationServerConfigs { - valConfFetcher := func() *rpcclient.ClientConfig { return &serverConfig } - validationSpawners[i] = server_api.NewValidationClient(valConfFetcher, stack) - } + var validationSpawners []validator.ValidationSpawner redisValClient, err := server_api.NewRedisValidationClient(&config().RedisValidationClientConfig) if err != nil { log.Error("Creating redis validation client", "error", err) } else { validationSpawners = append(validationSpawners, redisValClient) } + for _, serverConfig := range config().ValidationServerConfigs { + valConfFetcher := func() *rpcclient.ClientConfig { return &serverConfig } + validationSpawners = append(validationSpawners, server_api.NewValidationClient(valConfFetcher, stack)) + } - valConfFetcher := func() *rpcclient.ClientConfig { return &config().ValidationServerConfigs[0] } - execClient := server_api.NewExecutionClient(valConfFetcher, stack) validator := &StatelessBlockValidator{ config: config(), - execSpawner: execClient, recorder: recorder, validationSpawners: validationSpawners, inboxReader: inboxReader, @@ -221,6 +217,12 @@ func NewStatelessBlockValidator( daService: das, blobReader: blobReader, } + if len(config().ValidationServerConfigs) != 0 { + valConfFetcher := func() *rpcclient.ClientConfig { + return &config().ValidationServerConfigs[0] + } + validator.execSpawner = server_api.NewExecutionClient(valConfFetcher, stack) + } return validator, nil } @@ -425,15 +427,17 @@ func (v *StatelessBlockValidator) OverrideRecorder(t *testing.T, recorder execut } func (v *StatelessBlockValidator) Start(ctx_in context.Context) error { - err := v.execSpawner.Start(ctx_in) - if err != nil { - return err - } for _, spawner := range v.validationSpawners { if err := spawner.Start(ctx_in); err != nil { return err } } + if v.execSpawner == nil { + return nil + } + if err := v.execSpawner.Start(ctx_in); err != nil { + return err + } if v.config.PendingUpgradeModuleRoot != "" { if v.config.PendingUpgradeModuleRoot == "latest" { latest, err := v.execSpawner.LatestWasmModuleRoot().Await(ctx_in) @@ -453,7 +457,9 @@ func (v *StatelessBlockValidator) Start(ctx_in context.Context) error { } func (v *StatelessBlockValidator) Stop() { - v.execSpawner.Stop() + if v.execSpawner != nil { + v.execSpawner.Stop() + } for _, spawner := range v.validationSpawners { spawner.Stop() } diff --git a/system_tests/block_validator_test.go b/system_tests/block_validator_test.go index 1fcf2bab34..fa2fd238d5 100644 --- a/system_tests/block_validator_test.go +++ b/system_tests/block_validator_test.go @@ -26,6 +26,8 @@ import ( "github.com/offchainlabs/nitro/solgen/go/mocksgen" "github.com/offchainlabs/nitro/solgen/go/precompilesgen" "github.com/offchainlabs/nitro/util/arbmath" + "github.com/offchainlabs/nitro/util/redisutil" + "github.com/offchainlabs/nitro/validator/server_api" ) type workloadType uint @@ -37,7 +39,9 @@ const ( upgradeArbOs ) -func testBlockValidatorSimple(t *testing.T, dasModeString string, workloadLoops int, workload workloadType, arbitrator bool) { +var moduleRoot = "0xe5059c8450e490232bf1ffe02b7cf056349dccea517c8ac7c6d28a0e91ae68cd" + +func testBlockValidatorSimple(t *testing.T, dasModeString string, workloadLoops int, workload workloadType, arbitrator bool, useRedisStreams bool) { t.Parallel() ctx, cancel := context.WithCancel(context.Background()) defer cancel() @@ -67,7 +71,18 @@ func testBlockValidatorSimple(t *testing.T, dasModeString string, workloadLoops validatorConfig.BlockValidator.Enable = true validatorConfig.DataAvailability = l1NodeConfigA.DataAvailability validatorConfig.DataAvailability.RPCAggregator.Enable = false - AddDefaultValNode(t, ctx, validatorConfig, !arbitrator) + redisURL := "" + if useRedisStreams { + redisURL = redisutil.CreateTestRedis(ctx, t) + validatorConfig.BlockValidator.RedisValidationClientConfig = server_api.DefaultRedisValidationClientConfig + validatorConfig.BlockValidator.RedisValidationClientConfig.ModuleRoots = []string{moduleRoot} + stream := server_api.RedisStreamForRoot(common.HexToHash(moduleRoot)) + validatorConfig.BlockValidator.RedisValidationClientConfig.RedisStream = stream + validatorConfig.BlockValidator.RedisValidationClientConfig.RedisURL = redisURL + } + + AddDefaultValNode(t, ctx, validatorConfig, !arbitrator, redisURL) + testClientB, cleanupB := builder.Build2ndNode(t, &SecondNodeParams{nodeConfig: validatorConfig}) defer cleanupB() builder.L2Info.GenerateAccount("User2") @@ -239,17 +254,21 @@ func testBlockValidatorSimple(t *testing.T, dasModeString string, workloadLoops } func TestBlockValidatorSimpleOnchainUpgradeArbOs(t *testing.T) { - testBlockValidatorSimple(t, "onchain", 1, upgradeArbOs, true) + testBlockValidatorSimple(t, "onchain", 1, upgradeArbOs, true, false) } func TestBlockValidatorSimpleOnchain(t *testing.T) { - testBlockValidatorSimple(t, "onchain", 1, ethSend, true) + testBlockValidatorSimple(t, "onchain", 1, ethSend, true, false) +} + +func TestBlockValidatorSimpleOnchainWithRedisStreams(t *testing.T) { + testBlockValidatorSimple(t, "onchain", 1, ethSend, true, true) } func TestBlockValidatorSimpleLocalDAS(t *testing.T) { - testBlockValidatorSimple(t, "files", 1, ethSend, true) + testBlockValidatorSimple(t, "files", 1, ethSend, true, false) } func TestBlockValidatorSimpleJITOnchain(t *testing.T) { - testBlockValidatorSimple(t, "files", 8, smallContract, false) + testBlockValidatorSimple(t, "files", 8, smallContract, false, false) } diff --git a/system_tests/common_test.go b/system_tests/common_test.go index cd65cd2edc..6008f57ed2 100644 --- a/system_tests/common_test.go +++ b/system_tests/common_test.go @@ -16,6 +16,7 @@ import ( "testing" "time" + "github.com/go-redis/redis/v8" "github.com/offchainlabs/nitro/arbos/arbostypes" "github.com/offchainlabs/nitro/arbos/util" "github.com/offchainlabs/nitro/arbstate" @@ -27,8 +28,10 @@ import ( "github.com/offchainlabs/nitro/execution/gethexec" "github.com/offchainlabs/nitro/util/arbmath" "github.com/offchainlabs/nitro/util/headerreader" + "github.com/offchainlabs/nitro/util/redisutil" "github.com/offchainlabs/nitro/util/signature" "github.com/offchainlabs/nitro/validator/server_api" + "github.com/offchainlabs/nitro/validator/server_api/validation" "github.com/offchainlabs/nitro/validator/server_common" "github.com/offchainlabs/nitro/validator/valnode" @@ -504,6 +507,24 @@ func createStackConfigForTest(dataDir string) *node.Config { return &stackConf } +func createGroup(ctx context.Context, t *testing.T, streamName string, client redis.UniversalClient) { + t.Helper() + // Stream name and group name are the same. + if _, err := client.XGroupCreateMkStream(ctx, streamName, streamName, "$").Result(); err != nil { + log.Debug("Error creating stream group: %v", err) + } +} + +func destroyGroup(ctx context.Context, t *testing.T, streamName string, client redis.UniversalClient) { + t.Helper() + if client == nil { + return + } + if _, err := client.XGroupDestroy(ctx, streamName, streamName).Result(); err != nil { + log.Debug("Error destroying a stream group", "error", err) + } +} + func createTestValidationNode(t *testing.T, ctx context.Context, config *valnode.Config) (*valnode.ValidationNode, *node.Node) { stackConf := node.DefaultConfig stackConf.HTTPPort = 0 @@ -556,19 +577,35 @@ func StaticFetcherFrom[T any](t *testing.T, config *T) func() *T { } func configByValidationNode(t *testing.T, clientConfig *arbnode.Config, valStack *node.Node) { + if len(clientConfig.BlockValidator.ValidationServerConfigs) == 0 { + return + } clientConfig.BlockValidator.ValidationServerConfigs[0].URL = valStack.WSEndpoint() clientConfig.BlockValidator.ValidationServerConfigs[0].JWTSecret = "" } -func AddDefaultValNode(t *testing.T, ctx context.Context, nodeConfig *arbnode.Config, useJit bool) { +func AddDefaultValNode(t *testing.T, ctx context.Context, nodeConfig *arbnode.Config, useJit bool, redisURL string) { if !nodeConfig.ValidatorRequired() { return } - if nodeConfig.BlockValidator.ValidationServerConfigs[0].URL != "" { + if len(nodeConfig.BlockValidator.ValidationServerConfigs) > 0 && nodeConfig.BlockValidator.ValidationServerConfigs[0].URL != "" { return } conf := valnode.TestValidationConfig conf.UseJit = useJit + // Enable redis streams when URL is specified + if redisURL != "" { + conf.Arbitrator.RedisValidationServerConfig = validation.DefaultRedisValidationServerConfig + redisStream := server_api.RedisStreamForRoot(common.HexToHash(moduleRoot)) + redisClient, err := redisutil.RedisClientFromURL(redisURL) + if err != nil { + t.Fatalf("Error creating redis coordinator: %v", err) + } + createGroup(ctx, t, redisStream, redisClient) + conf.Arbitrator.RedisValidationServerConfig.RedisURL = redisURL + conf.Arbitrator.RedisValidationServerConfig.ModuleRoots = []string{moduleRoot} + t.Cleanup(func() { destroyGroup(ctx, t, redisStream, redisClient) }) + } _, valStack := createTestValidationNode(t, ctx, &conf) configByValidationNode(t, nodeConfig, valStack) } @@ -798,7 +835,7 @@ func createTestNodeWithL1( execConfig.Sequencer.Enable = false } - AddDefaultValNode(t, ctx, nodeConfig, true) + AddDefaultValNode(t, ctx, nodeConfig, true, "") Require(t, execConfig.Validate()) execConfigFetcher := func() *gethexec.Config { return execConfig } @@ -833,7 +870,7 @@ func createTestNode( feedErrChan := make(chan error, 10) - AddDefaultValNode(t, ctx, nodeConfig, true) + AddDefaultValNode(t, ctx, nodeConfig, true, "") l2info, stack, chainDb, arbDb, blockchain := createL2BlockChain(t, l2Info, "", chainConfig, &execConfig.Caching) @@ -939,7 +976,7 @@ func Create2ndNodeWithConfig( l2blockchain, err := gethexec.WriteOrTestBlockChain(l2chainDb, coreCacheConfig, initReader, chainConfig, initMessage, gethexec.ConfigDefaultTest().TxLookupLimit, 0) Require(t, err) - AddDefaultValNode(t, ctx, nodeConfig, true) + AddDefaultValNode(t, ctx, nodeConfig, true, "") Require(t, execConfig.Validate()) Require(t, nodeConfig.Validate()) diff --git a/validator/server_api/redisconsumer.go b/validator/server_api/redisconsumer.go index bc40d19d73..45ae842287 100644 --- a/validator/server_api/redisconsumer.go +++ b/validator/server_api/redisconsumer.go @@ -24,7 +24,7 @@ type RedisValidationServer struct { consumers map[common.Hash]*pubsub.Consumer[*validator.ValidationInput, validator.GoGlobalState] } -func NewRedisValidationServer(cfg *validation.RedisValidationServerConfig) (*RedisValidationServer, error) { +func NewRedisValidationServer(cfg *validation.RedisValidationServerConfig, spawner validator.ValidationSpawner) (*RedisValidationServer, error) { if cfg.RedisURL == "" { return nil, fmt.Errorf("redis url cannot be empty") } @@ -35,7 +35,7 @@ func NewRedisValidationServer(cfg *validation.RedisValidationServerConfig) (*Red consumers := make(map[common.Hash]*pubsub.Consumer[*validator.ValidationInput, validator.GoGlobalState]) for _, hash := range cfg.ModuleRoots { mr := common.HexToHash(hash) - c, err := pubsub.NewConsumer[*validator.ValidationInput, validator.GoGlobalState](redisClient, redisStreamForRoot(mr), &cfg.ConsumerConfig) + c, err := pubsub.NewConsumer[*validator.ValidationInput, validator.GoGlobalState](redisClient, RedisStreamForRoot(mr), &cfg.ConsumerConfig) if err != nil { return nil, fmt.Errorf("creating consumer for validation: %w", err) } @@ -43,6 +43,7 @@ func NewRedisValidationServer(cfg *validation.RedisValidationServerConfig) (*Red } return &RedisValidationServer{ consumers: consumers, + spawner: spawner, }, nil } @@ -57,6 +58,10 @@ func (s *RedisValidationServer) Start(ctx_in context.Context) { log.Error("Consuming request", "error", err) return 0 } + if req == nil { + // There's nothing in the queue. + return time.Second + } valRun := s.spawner.Launch(req.Value, moduleRoot) res, err := valRun.Await(ctx) if err != nil { diff --git a/validator/server_api/redisproducer.go b/validator/server_api/redisproducer.go index 5540cd169f..99c9bcce9a 100644 --- a/validator/server_api/redisproducer.go +++ b/validator/server_api/redisproducer.go @@ -21,10 +21,14 @@ type RedisValidationClientConfig struct { RedisURL string `koanf:"redis-url"` RedisStream string `koanf:"redis-stream"` ProducerConfig pubsub.ProducerConfig `koanf:"producer-config"` - // Supported wasm module roots. + // Supported wasm module roots, when the list is empty this is disabled. ModuleRoots []string `koanf:"module-roots"` } +func (c RedisValidationClientConfig) Enabled() bool { + return len(c.ModuleRoots) > 0 +} + var DefaultRedisValidationClientConfig = RedisValidationClientConfig{ Name: "redis validation client", Room: 2, @@ -58,7 +62,7 @@ type RedisValidationClient struct { producers map[common.Hash]*pubsub.Producer[*validator.ValidationInput, validator.GoGlobalState] } -func redisStreamForRoot(moduleRoot common.Hash) string { +func RedisStreamForRoot(moduleRoot common.Hash) string { return fmt.Sprintf("stream:%s", moduleRoot.Hex()) } @@ -75,10 +79,13 @@ func NewRedisValidationClient(cfg *RedisValidationClientConfig) (*RedisValidatio if err != nil { return nil, err } + if len(cfg.ModuleRoots) == 0 { + return nil, fmt.Errorf("moduleRoots must be specified to enable redis streams") + } for _, hash := range cfg.ModuleRoots { mr := common.HexToHash(hash) p, err := pubsub.NewProducer[*validator.ValidationInput, validator.GoGlobalState]( - redisClient, redisStreamForRoot(mr), &cfg.ProducerConfig) + redisClient, RedisStreamForRoot(mr), &cfg.ProducerConfig) if err != nil { return nil, fmt.Errorf("creating producer for validation: %w", err) } diff --git a/validator/server_api/validation/validation.go b/validator/server_api/validation/validation.go index 9cab29bde7..08d92085d6 100644 --- a/validator/server_api/validation/validation.go +++ b/validator/server_api/validation/validation.go @@ -33,7 +33,6 @@ type BatchInfoJson struct { type RedisValidationServerConfig struct { RedisURL string `koanf:"redis-url"` - RedisStream string `koanf:"redis-stream"` ConsumerConfig pubsub.ConsumerConfig `koanf:"consumer-config"` // Supported wasm module roots. ModuleRoots []string `koanf:"module-roots"` @@ -41,14 +40,12 @@ type RedisValidationServerConfig struct { var DefaultRedisValidationServerConfig = RedisValidationServerConfig{ RedisURL: "", - RedisStream: "", ConsumerConfig: pubsub.DefaultConsumerConfig, ModuleRoots: []string{}, } var TestRedisValidationServerConfig = RedisValidationServerConfig{ RedisURL: "", - RedisStream: "", ConsumerConfig: pubsub.TestConsumerConfig, ModuleRoots: []string{}, } diff --git a/validator/server_api/validation_client.go b/validator/server_api/validation_client.go index d6143ca917..0148eac0dd 100644 --- a/validator/server_api/validation_client.go +++ b/validator/server_api/validation_client.go @@ -48,21 +48,20 @@ func (c *ValidationClient) Launch(entry *validator.ValidationInput, moduleRoot c func (c *ValidationClient) Start(ctx_in context.Context) error { c.StopWaiter.Start(ctx_in, c) ctx := c.GetContext() - err := c.client.Start(ctx) - if err != nil { - return err + if c.client != nil { + if err := c.client.Start(ctx); err != nil { + return err + } } var name string - err = c.client.CallContext(ctx, &name, Namespace+"_name") - if err != nil { + if err := c.client.CallContext(ctx, &name, Namespace+"_name"); err != nil { return err } if len(name) == 0 { return errors.New("couldn't read name from server") } var room int - err = c.client.CallContext(c.GetContext(), &room, Namespace+"_room") - if err != nil { + if err := c.client.CallContext(c.GetContext(), &room, Namespace+"_room"); err != nil { return err } if room < 2 { diff --git a/validator/valnode/valnode.go b/validator/valnode/valnode.go index 5b4986f9da..e42acd8ae4 100644 --- a/validator/valnode/valnode.go +++ b/validator/valnode/valnode.go @@ -119,7 +119,7 @@ func CreateValidationNode(configFetcher ValidationConfigFetcher, stack *node.Nod } else { serverAPI = server_api.NewExecutionServerAPI(arbSpawner, arbSpawner, arbConfigFetcher) } - redisConsumer, err := server_api.NewRedisValidationServer(&arbConfigFetcher().RedisValidationServerConfig) + redisConsumer, err := server_api.NewRedisValidationServer(&arbConfigFetcher().RedisValidationServerConfig, arbSpawner) if err != nil { log.Error("Creating new redis validation server", "error", err) } From db855d5d5cf5918d00f42472786c2bd39072afb7 Mon Sep 17 00:00:00 2001 From: Nodar Ambroladze Date: Fri, 19 Apr 2024 22:58:47 +0200 Subject: [PATCH 05/17] Move moduleRoot to common_test since block_validator_test isn't compiled in race mode --- system_tests/block_validator_test.go | 9 +++++---- system_tests/common_test.go | 6 ++++-- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/system_tests/block_validator_test.go b/system_tests/block_validator_test.go index fa2fd238d5..ebc9ec9b92 100644 --- a/system_tests/block_validator_test.go +++ b/system_tests/block_validator_test.go @@ -39,8 +39,6 @@ const ( upgradeArbOs ) -var moduleRoot = "0xe5059c8450e490232bf1ffe02b7cf056349dccea517c8ac7c6d28a0e91ae68cd" - func testBlockValidatorSimple(t *testing.T, dasModeString string, workloadLoops int, workload workloadType, arbitrator bool, useRedisStreams bool) { t.Parallel() ctx, cancel := context.WithCancel(context.Background()) @@ -75,8 +73,8 @@ func testBlockValidatorSimple(t *testing.T, dasModeString string, workloadLoops if useRedisStreams { redisURL = redisutil.CreateTestRedis(ctx, t) validatorConfig.BlockValidator.RedisValidationClientConfig = server_api.DefaultRedisValidationClientConfig - validatorConfig.BlockValidator.RedisValidationClientConfig.ModuleRoots = []string{moduleRoot} - stream := server_api.RedisStreamForRoot(common.HexToHash(moduleRoot)) + validatorConfig.BlockValidator.RedisValidationClientConfig.ModuleRoots = []string{wasmModuleRoot} + stream := server_api.RedisStreamForRoot(common.HexToHash(wasmModuleRoot)) validatorConfig.BlockValidator.RedisValidationClientConfig.RedisStream = stream validatorConfig.BlockValidator.RedisValidationClientConfig.RedisURL = redisURL } @@ -84,6 +82,9 @@ func testBlockValidatorSimple(t *testing.T, dasModeString string, workloadLoops AddDefaultValNode(t, ctx, validatorConfig, !arbitrator, redisURL) testClientB, cleanupB := builder.Build2ndNode(t, &SecondNodeParams{nodeConfig: validatorConfig}) + if useRedisStreams { + testClientB.ConsensusNode.BlockValidator.SetCurrentWasmModuleRoot(common.HexToHash(wasmModuleRoot)) + } defer cleanupB() builder.L2Info.GenerateAccount("User2") diff --git a/system_tests/common_test.go b/system_tests/common_test.go index 6008f57ed2..ac33043917 100644 --- a/system_tests/common_test.go +++ b/system_tests/common_test.go @@ -72,6 +72,8 @@ import ( type info = *BlockchainTestInfo type client = arbutil.L1Interface +const wasmModuleRoot = "0xe5059c8450e490232bf1ffe02b7cf056349dccea517c8ac7c6d28a0e91ae68cd" + type SecondNodeParams struct { nodeConfig *arbnode.Config execConfig *gethexec.Config @@ -596,14 +598,14 @@ func AddDefaultValNode(t *testing.T, ctx context.Context, nodeConfig *arbnode.Co // Enable redis streams when URL is specified if redisURL != "" { conf.Arbitrator.RedisValidationServerConfig = validation.DefaultRedisValidationServerConfig - redisStream := server_api.RedisStreamForRoot(common.HexToHash(moduleRoot)) + redisStream := server_api.RedisStreamForRoot(common.HexToHash(wasmModuleRoot)) redisClient, err := redisutil.RedisClientFromURL(redisURL) if err != nil { t.Fatalf("Error creating redis coordinator: %v", err) } createGroup(ctx, t, redisStream, redisClient) conf.Arbitrator.RedisValidationServerConfig.RedisURL = redisURL - conf.Arbitrator.RedisValidationServerConfig.ModuleRoots = []string{moduleRoot} + conf.Arbitrator.RedisValidationServerConfig.ModuleRoots = []string{wasmModuleRoot} t.Cleanup(func() { destroyGroup(ctx, t, redisStream, redisClient) }) } _, valStack := createTestValidationNode(t, ctx, &conf) From 2f9cc14470caaf95d145326141e998ab7477fc01 Mon Sep 17 00:00:00 2001 From: Nodar Ambroladze Date: Fri, 19 Apr 2024 23:13:44 +0200 Subject: [PATCH 06/17] Fix linter error --- system_tests/block_validator_test.go | 4 +++- system_tests/common_test.go | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/system_tests/block_validator_test.go b/system_tests/block_validator_test.go index ebc9ec9b92..79eb735bec 100644 --- a/system_tests/block_validator_test.go +++ b/system_tests/block_validator_test.go @@ -83,7 +83,9 @@ func testBlockValidatorSimple(t *testing.T, dasModeString string, workloadLoops testClientB, cleanupB := builder.Build2ndNode(t, &SecondNodeParams{nodeConfig: validatorConfig}) if useRedisStreams { - testClientB.ConsensusNode.BlockValidator.SetCurrentWasmModuleRoot(common.HexToHash(wasmModuleRoot)) + if err := testClientB.ConsensusNode.BlockValidator.SetCurrentWasmModuleRoot(common.HexToHash(wasmModuleRoot)); err != nil { + t.Fatalf("Error setting wasm module root: %v", err) + } } defer cleanupB() builder.L2Info.GenerateAccount("User2") diff --git a/system_tests/common_test.go b/system_tests/common_test.go index ac33043917..e279337007 100644 --- a/system_tests/common_test.go +++ b/system_tests/common_test.go @@ -72,7 +72,7 @@ import ( type info = *BlockchainTestInfo type client = arbutil.L1Interface -const wasmModuleRoot = "0xe5059c8450e490232bf1ffe02b7cf056349dccea517c8ac7c6d28a0e91ae68cd" +const wasmModuleRoot = "0x0e5403827cef82bcbb6f4ba1b6f3d84edc5b4b8991b164f623ff2eacda768e35" type SecondNodeParams struct { nodeConfig *arbnode.Config From b990e16e6b6cedfc1fd96120d2c071a1d736f5d1 Mon Sep 17 00:00:00 2001 From: Nodar Ambroladze Date: Sat, 20 Apr 2024 00:17:38 +0200 Subject: [PATCH 07/17] Drop RedisStream from RedisValidationClientConfig, it's dereived from module roots --- system_tests/block_validator_test.go | 10 ++-------- system_tests/common_test.go | 16 ++++++++++------ validator/server_api/redisconsumer.go | 2 +- validator/server_api/redisproducer.go | 4 ---- 4 files changed, 13 insertions(+), 19 deletions(-) diff --git a/system_tests/block_validator_test.go b/system_tests/block_validator_test.go index 79eb735bec..b4b6e0134e 100644 --- a/system_tests/block_validator_test.go +++ b/system_tests/block_validator_test.go @@ -73,20 +73,14 @@ func testBlockValidatorSimple(t *testing.T, dasModeString string, workloadLoops if useRedisStreams { redisURL = redisutil.CreateTestRedis(ctx, t) validatorConfig.BlockValidator.RedisValidationClientConfig = server_api.DefaultRedisValidationClientConfig - validatorConfig.BlockValidator.RedisValidationClientConfig.ModuleRoots = []string{wasmModuleRoot} - stream := server_api.RedisStreamForRoot(common.HexToHash(wasmModuleRoot)) - validatorConfig.BlockValidator.RedisValidationClientConfig.RedisStream = stream + validatorConfig.BlockValidator.RedisValidationClientConfig.ModuleRoots = wasmModuleRoots validatorConfig.BlockValidator.RedisValidationClientConfig.RedisURL = redisURL + validatorConfig.BlockValidator.PendingUpgradeModuleRoot = wasmModuleRoots[0] } AddDefaultValNode(t, ctx, validatorConfig, !arbitrator, redisURL) testClientB, cleanupB := builder.Build2ndNode(t, &SecondNodeParams{nodeConfig: validatorConfig}) - if useRedisStreams { - if err := testClientB.ConsensusNode.BlockValidator.SetCurrentWasmModuleRoot(common.HexToHash(wasmModuleRoot)); err != nil { - t.Fatalf("Error setting wasm module root: %v", err) - } - } defer cleanupB() builder.L2Info.GenerateAccount("User2") diff --git a/system_tests/common_test.go b/system_tests/common_test.go index e279337007..a6db78a6c6 100644 --- a/system_tests/common_test.go +++ b/system_tests/common_test.go @@ -72,7 +72,9 @@ import ( type info = *BlockchainTestInfo type client = arbutil.L1Interface -const wasmModuleRoot = "0x0e5403827cef82bcbb6f4ba1b6f3d84edc5b4b8991b164f623ff2eacda768e35" +var wasmModuleRoots = []string{ + "0x0e5403827cef82bcbb6f4ba1b6f3d84edc5b4b8991b164f623ff2eacda768e35", +} type SecondNodeParams struct { nodeConfig *arbnode.Config @@ -598,15 +600,17 @@ func AddDefaultValNode(t *testing.T, ctx context.Context, nodeConfig *arbnode.Co // Enable redis streams when URL is specified if redisURL != "" { conf.Arbitrator.RedisValidationServerConfig = validation.DefaultRedisValidationServerConfig - redisStream := server_api.RedisStreamForRoot(common.HexToHash(wasmModuleRoot)) redisClient, err := redisutil.RedisClientFromURL(redisURL) if err != nil { t.Fatalf("Error creating redis coordinator: %v", err) } - createGroup(ctx, t, redisStream, redisClient) - conf.Arbitrator.RedisValidationServerConfig.RedisURL = redisURL - conf.Arbitrator.RedisValidationServerConfig.ModuleRoots = []string{wasmModuleRoot} - t.Cleanup(func() { destroyGroup(ctx, t, redisStream, redisClient) }) + for _, rootModule := range wasmModuleRoots { + redisStream := server_api.RedisStreamForRoot(common.HexToHash(rootModule)) + createGroup(ctx, t, redisStream, redisClient) + conf.Arbitrator.RedisValidationServerConfig.RedisURL = redisURL + t.Cleanup(func() { destroyGroup(ctx, t, redisStream, redisClient) }) + } + conf.Arbitrator.RedisValidationServerConfig.ModuleRoots = wasmModuleRoots } _, valStack := createTestValidationNode(t, ctx, &conf) configByValidationNode(t, nodeConfig, valStack) diff --git a/validator/server_api/redisconsumer.go b/validator/server_api/redisconsumer.go index 45ae842287..d87914380e 100644 --- a/validator/server_api/redisconsumer.go +++ b/validator/server_api/redisconsumer.go @@ -65,7 +65,7 @@ func (s *RedisValidationServer) Start(ctx_in context.Context) { valRun := s.spawner.Launch(req.Value, moduleRoot) res, err := valRun.Await(ctx) if err != nil { - log.Error("Error validating", "input", "request value", req.Value, "error", err) + log.Error("Error validating", "request value", req.Value, "error", err) return 0 } if err := c.SetResult(ctx, req.ID, res); err != nil { diff --git a/validator/server_api/redisproducer.go b/validator/server_api/redisproducer.go index 99c9bcce9a..cafef7e778 100644 --- a/validator/server_api/redisproducer.go +++ b/validator/server_api/redisproducer.go @@ -19,7 +19,6 @@ type RedisValidationClientConfig struct { Name string `koanf:"name"` Room int32 `koanf:"room"` RedisURL string `koanf:"redis-url"` - RedisStream string `koanf:"redis-stream"` ProducerConfig pubsub.ProducerConfig `koanf:"producer-config"` // Supported wasm module roots, when the list is empty this is disabled. ModuleRoots []string `koanf:"module-roots"` @@ -33,7 +32,6 @@ var DefaultRedisValidationClientConfig = RedisValidationClientConfig{ Name: "redis validation client", Room: 2, RedisURL: "", - RedisStream: "", ProducerConfig: pubsub.DefaultProducerConfig, } @@ -41,14 +39,12 @@ var TestRedisValidationClientConfig = RedisValidationClientConfig{ Name: "test redis validation client", Room: 2, RedisURL: "", - RedisStream: "", ProducerConfig: pubsub.TestProducerConfig, } func RedisValidationClientConfigAddOptions(prefix string, f *pflag.FlagSet) { f.String(prefix+".name", DefaultRedisValidationClientConfig.Name, "validation client name") f.Int32(prefix+".room", DefaultRedisValidationClientConfig.Room, "validation client room") - f.String(prefix+".redis-stream", DefaultRedisValidationClientConfig.RedisStream, "redis stream name") pubsub.ProducerAddConfigAddOptions(prefix+".producer-config", f) f.StringSlice(prefix+".module-roots", nil, "Supported module root hashes") } From 5b98d6f290f7a487051601f988c4aa5a64b7d650 Mon Sep 17 00:00:00 2001 From: Nodar Ambroladze Date: Sat, 20 Apr 2024 00:40:22 +0200 Subject: [PATCH 08/17] Error out when currentModuleRoot is latest and execSpanner isn't initialized --- staker/block_validator.go | 7 ++++--- system_tests/block_validator_test.go | 2 +- system_tests/common_test.go | 3 ++- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/staker/block_validator.go b/staker/block_validator.go index 1ec160c558..5cff19ba31 100644 --- a/staker/block_validator.go +++ b/staker/block_validator.go @@ -1033,12 +1033,13 @@ func (v *BlockValidator) Reorg(ctx context.Context, count arbutil.MessageIndex) // Initialize must be called after SetCurrentWasmModuleRoot sets the current one func (v *BlockValidator) Initialize(ctx context.Context) error { config := v.config() - if config.RedisValidationClientConfig.Enabled() && v.execSpawner == nil { - return nil - } + currentModuleRoot := config.CurrentModuleRoot switch currentModuleRoot { case "latest": + if v.execSpawner == nil { + return fmt.Errorf(`execution spawner is nil while current module root is "latest"`) + } latest, err := v.execSpawner.LatestWasmModuleRoot().Await(ctx) if err != nil { return err diff --git a/system_tests/block_validator_test.go b/system_tests/block_validator_test.go index b4b6e0134e..b472ec2a3c 100644 --- a/system_tests/block_validator_test.go +++ b/system_tests/block_validator_test.go @@ -75,7 +75,7 @@ func testBlockValidatorSimple(t *testing.T, dasModeString string, workloadLoops validatorConfig.BlockValidator.RedisValidationClientConfig = server_api.DefaultRedisValidationClientConfig validatorConfig.BlockValidator.RedisValidationClientConfig.ModuleRoots = wasmModuleRoots validatorConfig.BlockValidator.RedisValidationClientConfig.RedisURL = redisURL - validatorConfig.BlockValidator.PendingUpgradeModuleRoot = wasmModuleRoots[0] + validatorConfig.BlockValidator.CurrentModuleRoot = wasmModuleRoots[0] } AddDefaultValNode(t, ctx, validatorConfig, !arbitrator, redisURL) diff --git a/system_tests/common_test.go b/system_tests/common_test.go index a6db78a6c6..91b08fdeaf 100644 --- a/system_tests/common_test.go +++ b/system_tests/common_test.go @@ -73,7 +73,8 @@ type info = *BlockchainTestInfo type client = arbutil.L1Interface var wasmModuleRoots = []string{ - "0x0e5403827cef82bcbb6f4ba1b6f3d84edc5b4b8991b164f623ff2eacda768e35", + "0xb1e1f56cdcb7453d9416e9b242ded14aa4324674f1173e86fec9b85e923284e7", + // "0x0e5403827cef82bcbb6f4ba1b6f3d84edc5b4b8991b164f623ff2eacda768e35", } type SecondNodeParams struct { From fb897bbe70e869a971c8d6da2ca9bd2befb302ce Mon Sep 17 00:00:00 2001 From: Nodar Ambroladze Date: Sat, 20 Apr 2024 01:22:45 +0200 Subject: [PATCH 09/17] Set rootModule dynamically --- system_tests/block_validator_test.go | 3 +-- system_tests/common_test.go | 26 ++++++++++++++------------ 2 files changed, 15 insertions(+), 14 deletions(-) diff --git a/system_tests/block_validator_test.go b/system_tests/block_validator_test.go index b472ec2a3c..68fcaa5ba4 100644 --- a/system_tests/block_validator_test.go +++ b/system_tests/block_validator_test.go @@ -73,9 +73,8 @@ func testBlockValidatorSimple(t *testing.T, dasModeString string, workloadLoops if useRedisStreams { redisURL = redisutil.CreateTestRedis(ctx, t) validatorConfig.BlockValidator.RedisValidationClientConfig = server_api.DefaultRedisValidationClientConfig - validatorConfig.BlockValidator.RedisValidationClientConfig.ModuleRoots = wasmModuleRoots + validatorConfig.BlockValidator.RedisValidationClientConfig.ModuleRoots = []string{currentRootModule(t).Hex()} validatorConfig.BlockValidator.RedisValidationClientConfig.RedisURL = redisURL - validatorConfig.BlockValidator.CurrentModuleRoot = wasmModuleRoots[0] } AddDefaultValNode(t, ctx, validatorConfig, !arbitrator, redisURL) diff --git a/system_tests/common_test.go b/system_tests/common_test.go index 91b08fdeaf..fb82ca5fa2 100644 --- a/system_tests/common_test.go +++ b/system_tests/common_test.go @@ -72,11 +72,6 @@ import ( type info = *BlockchainTestInfo type client = arbutil.L1Interface -var wasmModuleRoots = []string{ - "0xb1e1f56cdcb7453d9416e9b242ded14aa4324674f1173e86fec9b85e923284e7", - // "0x0e5403827cef82bcbb6f4ba1b6f3d84edc5b4b8991b164f623ff2eacda768e35", -} - type SecondNodeParams struct { nodeConfig *arbnode.Config execConfig *gethexec.Config @@ -589,6 +584,15 @@ func configByValidationNode(t *testing.T, clientConfig *arbnode.Config, valStack clientConfig.BlockValidator.ValidationServerConfigs[0].JWTSecret = "" } +func currentRootModule(t *testing.T) common.Hash { + t.Helper() + locator, err := server_common.NewMachineLocator("") + if err != nil { + t.Fatalf("Error creating machine locator: %v", err) + } + return locator.LatestWasmModuleRoot() +} + func AddDefaultValNode(t *testing.T, ctx context.Context, nodeConfig *arbnode.Config, useJit bool, redisURL string) { if !nodeConfig.ValidatorRequired() { return @@ -605,13 +609,11 @@ func AddDefaultValNode(t *testing.T, ctx context.Context, nodeConfig *arbnode.Co if err != nil { t.Fatalf("Error creating redis coordinator: %v", err) } - for _, rootModule := range wasmModuleRoots { - redisStream := server_api.RedisStreamForRoot(common.HexToHash(rootModule)) - createGroup(ctx, t, redisStream, redisClient) - conf.Arbitrator.RedisValidationServerConfig.RedisURL = redisURL - t.Cleanup(func() { destroyGroup(ctx, t, redisStream, redisClient) }) - } - conf.Arbitrator.RedisValidationServerConfig.ModuleRoots = wasmModuleRoots + redisStream := server_api.RedisStreamForRoot(currentRootModule(t)) + createGroup(ctx, t, redisStream, redisClient) + conf.Arbitrator.RedisValidationServerConfig.RedisURL = redisURL + t.Cleanup(func() { destroyGroup(ctx, t, redisStream, redisClient) }) + conf.Arbitrator.RedisValidationServerConfig.ModuleRoots = []string{currentRootModule(t).Hex()} } _, valStack := createTestValidationNode(t, ctx, &conf) configByValidationNode(t, nodeConfig, valStack) From 738f04dcb70018fe748e69e4de84447cad340c62 Mon Sep 17 00:00:00 2001 From: Nodar Ambroladze Date: Sat, 20 Apr 2024 10:21:17 +0200 Subject: [PATCH 10/17] Introduce ExecutionServerConfig to BlockValidatorConfig, restructure validator packages --- pubsub/consumer.go | 7 -- pubsub/producer.go | 9 -- pubsub/pubsub_test.go | 8 +- staker/block_validator.go | 46 ++++---- staker/stateless_block_validator.go | 33 +++--- system_tests/block_validator_test.go | 6 +- system_tests/common_test.go | 25 ++--- system_tests/full_challenge_impl_test.go | 2 +- system_tests/validation_mock_test.go | 13 ++- .../{server_api => client}/redisproducer.go | 9 +- .../validation_client.go | 51 ++++++--- validator/server_api/json.go | 104 +++++++++--------- validator/server_api/validation/validation.go | 56 ---------- validator/server_arb/validator_spawner.go | 8 +- .../{server_api => valnode}/redisconsumer.go | 8 +- .../{server_api => valnode}/validation_api.go | 48 ++++++-- validator/valnode/valnode.go | 10 +- 17 files changed, 208 insertions(+), 235 deletions(-) rename validator/{server_api => client}/redisproducer.go (95%) rename validator/{server_api => client}/validation_client.go (72%) delete mode 100644 validator/server_api/validation/validation.go rename validator/{server_api => valnode}/redisconsumer.go (90%) rename validator/{server_api => valnode}/validation_api.go (76%) diff --git a/pubsub/consumer.go b/pubsub/consumer.go index 5385b33979..7a5078ee00 100644 --- a/pubsub/consumer.go +++ b/pubsub/consumer.go @@ -22,13 +22,6 @@ type ConsumerConfig struct { KeepAliveTimeout time.Duration `koanf:"keepalive-timeout"` } -func (c ConsumerConfig) Clone() ConsumerConfig { - return ConsumerConfig{ - ResponseEntryTimeout: c.ResponseEntryTimeout, - KeepAliveTimeout: c.KeepAliveTimeout, - } -} - var DefaultConsumerConfig = ConsumerConfig{ ResponseEntryTimeout: time.Hour, KeepAliveTimeout: 5 * time.Minute, diff --git a/pubsub/producer.go b/pubsub/producer.go index debea81365..b00eec7f64 100644 --- a/pubsub/producer.go +++ b/pubsub/producer.go @@ -61,15 +61,6 @@ type ProducerConfig struct { CheckResultInterval time.Duration `koanf:"check-result-interval"` } -func (c ProducerConfig) Clone() ProducerConfig { - return ProducerConfig{ - EnableReproduce: c.EnableReproduce, - CheckPendingInterval: c.CheckPendingInterval, - KeepAliveTimeout: c.KeepAliveTimeout, - CheckResultInterval: c.CheckResultInterval, - } -} - var DefaultProducerConfig = ProducerConfig{ EnableReproduce: true, CheckPendingInterval: time.Second, diff --git a/pubsub/pubsub_test.go b/pubsub/pubsub_test.go index 949e532343..31f6d9e20a 100644 --- a/pubsub/pubsub_test.go +++ b/pubsub/pubsub_test.go @@ -28,7 +28,7 @@ type testResponse struct { Response string } -func createGroup(ctx context.Context, t *testing.T, streamName string, client redis.UniversalClient) { +func createRedisGroup(ctx context.Context, t *testing.T, streamName string, client redis.UniversalClient) { t.Helper() // Stream name and group name are the same. if _, err := client.XGroupCreateMkStream(ctx, streamName, streamName, "$").Result(); err != nil { @@ -36,7 +36,7 @@ func createGroup(ctx context.Context, t *testing.T, streamName string, client re } } -func destroyGroup(ctx context.Context, t *testing.T, streamName string, client redis.UniversalClient) { +func destroyRedisGroup(ctx context.Context, t *testing.T, streamName string, client redis.UniversalClient) { t.Helper() if _, err := client.XGroupDestroy(ctx, streamName, streamName).Result(); err != nil { log.Debug("Error destroying a stream group", "error", err) @@ -93,10 +93,10 @@ func newProducerConsumers(ctx context.Context, t *testing.T, opts ...configOpt) } consumers = append(consumers, c) } - createGroup(ctx, t, streamName, producer.client) + createRedisGroup(ctx, t, streamName, producer.client) t.Cleanup(func() { ctx := context.Background() - destroyGroup(ctx, t, streamName, producer.client) + destroyRedisGroup(ctx, t, streamName, producer.client) var keys []string for _, c := range consumers { keys = append(keys, c.heartBeatKey()) diff --git a/staker/block_validator.go b/staker/block_validator.go index 5cff19ba31..cd89ccf650 100644 --- a/staker/block_validator.go +++ b/staker/block_validator.go @@ -14,8 +14,6 @@ import ( "testing" "time" - flag "github.com/spf13/pflag" - "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/log" @@ -27,7 +25,9 @@ import ( "github.com/offchainlabs/nitro/util/rpcclient" "github.com/offchainlabs/nitro/util/stopwaiter" "github.com/offchainlabs/nitro/validator" - "github.com/offchainlabs/nitro/validator/server_api" + "github.com/spf13/pflag" + + validatorclient "github.com/offchainlabs/nitro/validator/client" ) var ( @@ -84,19 +84,20 @@ type BlockValidator struct { } type BlockValidatorConfig struct { - Enable bool `koanf:"enable"` - ValidationServer rpcclient.ClientConfig `koanf:"validation-server" reload:"hot"` - RedisValidationClientConfig server_api.RedisValidationClientConfig `koanf:"redis-validation-client-config"` - ValidationServerConfigs []rpcclient.ClientConfig `koanf:"validation-server-configs" reload:"hot"` - ValidationPoll time.Duration `koanf:"validation-poll" reload:"hot"` - PrerecordedBlocks uint64 `koanf:"prerecorded-blocks" reload:"hot"` - ForwardBlocks uint64 `koanf:"forward-blocks" reload:"hot"` - CurrentModuleRoot string `koanf:"current-module-root"` // TODO(magic) requires reinitialization on hot reload - PendingUpgradeModuleRoot string `koanf:"pending-upgrade-module-root"` // TODO(magic) requires StatelessBlockValidator recreation on hot reload - FailureIsFatal bool `koanf:"failure-is-fatal" reload:"hot"` - Dangerous BlockValidatorDangerousConfig `koanf:"dangerous"` - MemoryFreeLimit string `koanf:"memory-free-limit" reload:"hot"` - ValidationServerConfigsList string `koanf:"validation-server-configs-list" reload:"hot"` + Enable bool `koanf:"enable"` + ValidationServer rpcclient.ClientConfig `koanf:"validation-server" reload:"hot"` + RedisValidationClientConfig validatorclient.RedisValidationClientConfig `koanf:"redis-validation-client-config"` + ValidationServerConfigs []rpcclient.ClientConfig `koanf:"validation-server-configs" reload:"hot"` + ExecutionServerConfig rpcclient.ClientConfig `koanf:"execution-server-config" reload:"hot"` + ValidationPoll time.Duration `koanf:"validation-poll" reload:"hot"` + PrerecordedBlocks uint64 `koanf:"prerecorded-blocks" reload:"hot"` + ForwardBlocks uint64 `koanf:"forward-blocks" reload:"hot"` + CurrentModuleRoot string `koanf:"current-module-root"` // TODO(magic) requires reinitialization on hot reload + PendingUpgradeModuleRoot string `koanf:"pending-upgrade-module-root"` // TODO(magic) requires StatelessBlockValidator recreation on hot reload + FailureIsFatal bool `koanf:"failure-is-fatal" reload:"hot"` + Dangerous BlockValidatorDangerousConfig `koanf:"dangerous"` + MemoryFreeLimit string `koanf:"memory-free-limit" reload:"hot"` + ValidationServerConfigsList string `koanf:"validation-server-configs-list" reload:"hot"` memoryFreeLimit int } @@ -113,9 +114,8 @@ func (c *BlockValidatorConfig) Validate() error { } streamsEnabled := c.RedisValidationClientConfig.Enabled() if c.ValidationServerConfigs == nil { - if c.ValidationServerConfigsList == "default" { - c.ValidationServerConfigs = []rpcclient.ClientConfig{c.ValidationServer} - } else { + c.ValidationServerConfigs = []rpcclient.ClientConfig{c.ValidationServer} + if c.ValidationServerConfigsList != "default" { var validationServersConfigs []rpcclient.ClientConfig if err := json.Unmarshal([]byte(c.ValidationServerConfigsList), &validationServersConfigs); err != nil && !streamsEnabled { return fmt.Errorf("failed to parse block-validator validation-server-configs-list string: %w", err) @@ -131,6 +131,9 @@ func (c *BlockValidatorConfig) Validate() error { return fmt.Errorf("failed to validate one of the block-validator validation-server-configs. url: %s, err: %w", serverConfig.URL, err) } } + if err := c.ExecutionServerConfig.Validate(); err != nil { + return fmt.Errorf("validating execution server config: %w", err) + } return nil } @@ -140,7 +143,7 @@ type BlockValidatorDangerousConfig struct { type BlockValidatorConfigFetcher func() *BlockValidatorConfig -func BlockValidatorConfigAddOptions(prefix string, f *flag.FlagSet) { +func BlockValidatorConfigAddOptions(prefix string, f *pflag.FlagSet) { f.Bool(prefix+".enable", DefaultBlockValidatorConfig.Enable, "enable block-by-block validation") rpcclient.RPCClientAddOptions(prefix+".validation-server", f, &DefaultBlockValidatorConfig.ValidationServer) f.String(prefix+".validation-server-configs-list", DefaultBlockValidatorConfig.ValidationServerConfigsList, "array of validation rpc configs given as a json string. time duration should be supplied in number indicating nanoseconds") @@ -154,7 +157,7 @@ func BlockValidatorConfigAddOptions(prefix string, f *flag.FlagSet) { f.String(prefix+".memory-free-limit", DefaultBlockValidatorConfig.MemoryFreeLimit, "minimum free-memory limit after reaching which the blockvalidator pauses validation. Enabled by default as 1GB, to disable provide empty string") } -func BlockValidatorDangerousConfigAddOptions(prefix string, f *flag.FlagSet) { +func BlockValidatorDangerousConfigAddOptions(prefix string, f *pflag.FlagSet) { f.Bool(prefix+".reset-block-validation", DefaultBlockValidatorDangerousConfig.ResetBlockValidation, "resets block-by-block validation, starting again at genesis") } @@ -176,6 +179,7 @@ var TestBlockValidatorConfig = BlockValidatorConfig{ Enable: false, ValidationServer: rpcclient.TestClientConfig, ValidationServerConfigs: []rpcclient.ClientConfig{rpcclient.TestClientConfig}, + ExecutionServerConfig: rpcclient.TestClientConfig, ValidationPoll: 100 * time.Millisecond, ForwardBlocks: 128, PrerecordedBlocks: uint64(2 * runtime.NumCPU()), diff --git a/staker/stateless_block_validator.go b/staker/stateless_block_validator.go index 25d64fae35..eaa2bfb133 100644 --- a/staker/stateless_block_validator.go +++ b/staker/stateless_block_validator.go @@ -11,19 +11,19 @@ import ( "sync" "testing" - "github.com/offchainlabs/nitro/execution" - "github.com/offchainlabs/nitro/util/rpcclient" - "github.com/offchainlabs/nitro/validator/server_api" - - "github.com/offchainlabs/nitro/arbutil" - "github.com/offchainlabs/nitro/validator" - "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/node" + "github.com/offchainlabs/nitro/arbos/arbostypes" "github.com/offchainlabs/nitro/arbstate" + "github.com/offchainlabs/nitro/arbutil" + "github.com/offchainlabs/nitro/execution" + "github.com/offchainlabs/nitro/util/rpcclient" + "github.com/offchainlabs/nitro/validator" + + validatorclient "github.com/offchainlabs/nitro/validator/client" ) type StatelessBlockValidator struct { @@ -195,7 +195,7 @@ func NewStatelessBlockValidator( stack *node.Node, ) (*StatelessBlockValidator, error) { var validationSpawners []validator.ValidationSpawner - redisValClient, err := server_api.NewRedisValidationClient(&config().RedisValidationClientConfig) + redisValClient, err := validatorclient.NewRedisValidationClient(&config().RedisValidationClientConfig) if err != nil { log.Error("Creating redis validation client", "error", err) } else { @@ -203,7 +203,7 @@ func NewStatelessBlockValidator( } for _, serverConfig := range config().ValidationServerConfigs { valConfFetcher := func() *rpcclient.ClientConfig { return &serverConfig } - validationSpawners = append(validationSpawners, server_api.NewValidationClient(valConfFetcher, stack)) + validationSpawners = append(validationSpawners, validatorclient.NewValidationClient(valConfFetcher, stack)) } validator := &StatelessBlockValidator{ @@ -217,12 +217,10 @@ func NewStatelessBlockValidator( daService: das, blobReader: blobReader, } - if len(config().ValidationServerConfigs) != 0 { - valConfFetcher := func() *rpcclient.ClientConfig { - return &config().ValidationServerConfigs[0] - } - validator.execSpawner = server_api.NewExecutionClient(valConfFetcher, stack) + valConfFetcher := func() *rpcclient.ClientConfig { + return &config().ExecutionServerConfig } + validator.execSpawner = validatorclient.NewExecutionClient(valConfFetcher, stack) return validator, nil } @@ -432,9 +430,6 @@ func (v *StatelessBlockValidator) Start(ctx_in context.Context) error { return err } } - if v.execSpawner == nil { - return nil - } if err := v.execSpawner.Start(ctx_in); err != nil { return err } @@ -457,9 +452,7 @@ func (v *StatelessBlockValidator) Start(ctx_in context.Context) error { } func (v *StatelessBlockValidator) Stop() { - if v.execSpawner != nil { - v.execSpawner.Stop() - } + v.execSpawner.Stop() for _, spawner := range v.validationSpawners { spawner.Stop() } diff --git a/system_tests/block_validator_test.go b/system_tests/block_validator_test.go index 68fcaa5ba4..ed8438eb78 100644 --- a/system_tests/block_validator_test.go +++ b/system_tests/block_validator_test.go @@ -27,7 +27,8 @@ import ( "github.com/offchainlabs/nitro/solgen/go/precompilesgen" "github.com/offchainlabs/nitro/util/arbmath" "github.com/offchainlabs/nitro/util/redisutil" - "github.com/offchainlabs/nitro/validator/server_api" + + validatorclient "github.com/offchainlabs/nitro/validator/client" ) type workloadType uint @@ -72,9 +73,10 @@ func testBlockValidatorSimple(t *testing.T, dasModeString string, workloadLoops redisURL := "" if useRedisStreams { redisURL = redisutil.CreateTestRedis(ctx, t) - validatorConfig.BlockValidator.RedisValidationClientConfig = server_api.DefaultRedisValidationClientConfig + validatorConfig.BlockValidator.RedisValidationClientConfig = validatorclient.DefaultRedisValidationClientConfig validatorConfig.BlockValidator.RedisValidationClientConfig.ModuleRoots = []string{currentRootModule(t).Hex()} validatorConfig.BlockValidator.RedisValidationClientConfig.RedisURL = redisURL + validatorConfig.BlockValidator.ValidationServerConfigs = nil } AddDefaultValNode(t, ctx, validatorConfig, !arbitrator, redisURL) diff --git a/system_tests/common_test.go b/system_tests/common_test.go index fb82ca5fa2..54e40219f3 100644 --- a/system_tests/common_test.go +++ b/system_tests/common_test.go @@ -31,7 +31,6 @@ import ( "github.com/offchainlabs/nitro/util/redisutil" "github.com/offchainlabs/nitro/util/signature" "github.com/offchainlabs/nitro/validator/server_api" - "github.com/offchainlabs/nitro/validator/server_api/validation" "github.com/offchainlabs/nitro/validator/server_common" "github.com/offchainlabs/nitro/validator/valnode" @@ -507,7 +506,7 @@ func createStackConfigForTest(dataDir string) *node.Config { return &stackConf } -func createGroup(ctx context.Context, t *testing.T, streamName string, client redis.UniversalClient) { +func createRedisGroup(ctx context.Context, t *testing.T, streamName string, client redis.UniversalClient) { t.Helper() // Stream name and group name are the same. if _, err := client.XGroupCreateMkStream(ctx, streamName, streamName, "$").Result(); err != nil { @@ -515,7 +514,7 @@ func createGroup(ctx context.Context, t *testing.T, streamName string, client re } } -func destroyGroup(ctx context.Context, t *testing.T, streamName string, client redis.UniversalClient) { +func destroyRedisGroup(ctx context.Context, t *testing.T, streamName string, client redis.UniversalClient) { t.Helper() if client == nil { return @@ -576,12 +575,9 @@ func StaticFetcherFrom[T any](t *testing.T, config *T) func() *T { return func() *T { return &tCopy } } -func configByValidationNode(t *testing.T, clientConfig *arbnode.Config, valStack *node.Node) { - if len(clientConfig.BlockValidator.ValidationServerConfigs) == 0 { - return - } - clientConfig.BlockValidator.ValidationServerConfigs[0].URL = valStack.WSEndpoint() - clientConfig.BlockValidator.ValidationServerConfigs[0].JWTSecret = "" +func configByValidationNode(clientConfig *arbnode.Config, valStack *node.Node) { + clientConfig.BlockValidator.ExecutionServerConfig.URL = valStack.WSEndpoint() + clientConfig.BlockValidator.ExecutionServerConfig.JWTSecret = "" } func currentRootModule(t *testing.T) common.Hash { @@ -597,26 +593,23 @@ func AddDefaultValNode(t *testing.T, ctx context.Context, nodeConfig *arbnode.Co if !nodeConfig.ValidatorRequired() { return } - if len(nodeConfig.BlockValidator.ValidationServerConfigs) > 0 && nodeConfig.BlockValidator.ValidationServerConfigs[0].URL != "" { - return - } conf := valnode.TestValidationConfig conf.UseJit = useJit // Enable redis streams when URL is specified if redisURL != "" { - conf.Arbitrator.RedisValidationServerConfig = validation.DefaultRedisValidationServerConfig + conf.Arbitrator.RedisValidationServerConfig = server_api.DefaultRedisValidationServerConfig redisClient, err := redisutil.RedisClientFromURL(redisURL) if err != nil { t.Fatalf("Error creating redis coordinator: %v", err) } redisStream := server_api.RedisStreamForRoot(currentRootModule(t)) - createGroup(ctx, t, redisStream, redisClient) + createRedisGroup(ctx, t, redisStream, redisClient) conf.Arbitrator.RedisValidationServerConfig.RedisURL = redisURL - t.Cleanup(func() { destroyGroup(ctx, t, redisStream, redisClient) }) + t.Cleanup(func() { destroyRedisGroup(ctx, t, redisStream, redisClient) }) conf.Arbitrator.RedisValidationServerConfig.ModuleRoots = []string{currentRootModule(t).Hex()} } _, valStack := createTestValidationNode(t, ctx, &conf) - configByValidationNode(t, nodeConfig, valStack) + configByValidationNode(nodeConfig, valStack) } func createTestL1BlockChainWithConfig(t *testing.T, l1info info, stackConfig *node.Config) (info, *ethclient.Client, *eth.Ethereum, *node.Node) { diff --git a/system_tests/full_challenge_impl_test.go b/system_tests/full_challenge_impl_test.go index 03b6d690f1..af790c9a17 100644 --- a/system_tests/full_challenge_impl_test.go +++ b/system_tests/full_challenge_impl_test.go @@ -277,7 +277,7 @@ func RunChallengeTest(t *testing.T, asserterIsCorrect bool, useStubs bool, chall } else { _, valStack = createTestValidationNode(t, ctx, &valnode.TestValidationConfig) } - configByValidationNode(t, conf, valStack) + configByValidationNode(conf, valStack) fatalErrChan := make(chan error, 10) asserterRollupAddresses, initMessage := DeployOnTestL1(t, ctx, l1Info, l1Backend, chainConfig) diff --git a/system_tests/validation_mock_test.go b/system_tests/validation_mock_test.go index d9c302b33f..2deb99b09a 100644 --- a/system_tests/validation_mock_test.go +++ b/system_tests/validation_mock_test.go @@ -21,6 +21,9 @@ import ( "github.com/offchainlabs/nitro/validator" "github.com/offchainlabs/nitro/validator/server_api" "github.com/offchainlabs/nitro/validator/server_arb" + "github.com/offchainlabs/nitro/validator/valnode" + + validatorclient "github.com/offchainlabs/nitro/validator/client" ) type mockSpawner struct { @@ -150,7 +153,7 @@ func createMockValidationNode(t *testing.T, ctx context.Context, config *server_ } configFetcher := func() *server_arb.ArbitratorSpawnerConfig { return config } spawner := &mockSpawner{} - serverAPI := server_api.NewExecutionServerAPI(spawner, spawner, configFetcher) + serverAPI := valnode.NewExecutionServerAPI(spawner, spawner, configFetcher) valAPIs := []rpc.API{{ Namespace: server_api.Namespace, @@ -181,7 +184,7 @@ func TestValidationServerAPI(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() _, validationDefault := createMockValidationNode(t, ctx, nil) - client := server_api.NewExecutionClient(StaticFetcherFrom(t, &rpcclient.TestClientConfig), validationDefault) + client := validatorclient.NewExecutionClient(StaticFetcherFrom(t, &rpcclient.TestClientConfig), validationDefault) err := client.Start(ctx) Require(t, err) @@ -247,7 +250,7 @@ func TestValidationClientRoom(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() mockSpawner, spawnerStack := createMockValidationNode(t, ctx, nil) - client := server_api.NewExecutionClient(StaticFetcherFrom(t, &rpcclient.TestClientConfig), spawnerStack) + client := validatorclient.NewExecutionClient(StaticFetcherFrom(t, &rpcclient.TestClientConfig), spawnerStack) err := client.Start(ctx) Require(t, err) @@ -334,10 +337,10 @@ func TestExecutionKeepAlive(t *testing.T) { _, validationShortTO := createMockValidationNode(t, ctx, &shortTimeoutConfig) configFetcher := StaticFetcherFrom(t, &rpcclient.TestClientConfig) - clientDefault := server_api.NewExecutionClient(configFetcher, validationDefault) + clientDefault := validatorclient.NewExecutionClient(configFetcher, validationDefault) err := clientDefault.Start(ctx) Require(t, err) - clientShortTO := server_api.NewExecutionClient(configFetcher, validationShortTO) + clientShortTO := validatorclient.NewExecutionClient(configFetcher, validationShortTO) err = clientShortTO.Start(ctx) Require(t, err) diff --git a/validator/server_api/redisproducer.go b/validator/client/redisproducer.go similarity index 95% rename from validator/server_api/redisproducer.go rename to validator/client/redisproducer.go index cafef7e778..cfe738f649 100644 --- a/validator/server_api/redisproducer.go +++ b/validator/client/redisproducer.go @@ -1,4 +1,4 @@ -package server_api +package client import ( "context" @@ -11,6 +11,7 @@ import ( "github.com/offchainlabs/nitro/util/redisutil" "github.com/offchainlabs/nitro/util/stopwaiter" "github.com/offchainlabs/nitro/validator" + "github.com/offchainlabs/nitro/validator/server_api" "github.com/offchainlabs/nitro/validator/server_common" "github.com/spf13/pflag" ) @@ -58,10 +59,6 @@ type RedisValidationClient struct { producers map[common.Hash]*pubsub.Producer[*validator.ValidationInput, validator.GoGlobalState] } -func RedisStreamForRoot(moduleRoot common.Hash) string { - return fmt.Sprintf("stream:%s", moduleRoot.Hex()) -} - func NewRedisValidationClient(cfg *RedisValidationClientConfig) (*RedisValidationClient, error) { res := &RedisValidationClient{ name: cfg.Name, @@ -81,7 +78,7 @@ func NewRedisValidationClient(cfg *RedisValidationClientConfig) (*RedisValidatio for _, hash := range cfg.ModuleRoots { mr := common.HexToHash(hash) p, err := pubsub.NewProducer[*validator.ValidationInput, validator.GoGlobalState]( - redisClient, RedisStreamForRoot(mr), &cfg.ProducerConfig) + redisClient, server_api.RedisStreamForRoot(mr), &cfg.ProducerConfig) if err != nil { return nil, fmt.Errorf("creating producer for validation: %w", err) } diff --git a/validator/server_api/validation_client.go b/validator/client/validation_client.go similarity index 72% rename from validator/server_api/validation_client.go rename to validator/client/validation_client.go index 0148eac0dd..ffa6ca9bd6 100644 --- a/validator/server_api/validation_client.go +++ b/validator/client/validation_client.go @@ -1,4 +1,4 @@ -package server_api +package client import ( "context" @@ -7,12 +7,15 @@ import ( "sync/atomic" "time" + "github.com/offchainlabs/nitro/arbutil" "github.com/offchainlabs/nitro/validator" "github.com/offchainlabs/nitro/util/containers" + "github.com/offchainlabs/nitro/util/jsonapi" "github.com/offchainlabs/nitro/util/rpcclient" "github.com/offchainlabs/nitro/util/stopwaiter" + "github.com/offchainlabs/nitro/validator/server_api" "github.com/offchainlabs/nitro/validator/server_common" "github.com/ethereum/go-ethereum/common" @@ -38,7 +41,7 @@ func (c *ValidationClient) Launch(entry *validator.ValidationInput, moduleRoot c promise := stopwaiter.LaunchPromiseThread[validator.GoGlobalState](c, func(ctx context.Context) (validator.GoGlobalState, error) { input := ValidationInputToJson(entry) var res validator.GoGlobalState - err := c.client.CallContext(ctx, &res, Namespace+"_validate", input, moduleRoot) + err := c.client.CallContext(ctx, &res, server_api.Namespace+"_validate", input, moduleRoot) atomic.AddInt32(&c.room, 1) return res, err }) @@ -54,14 +57,14 @@ func (c *ValidationClient) Start(ctx_in context.Context) error { } } var name string - if err := c.client.CallContext(ctx, &name, Namespace+"_name"); err != nil { + if err := c.client.CallContext(ctx, &name, server_api.Namespace+"_name"); err != nil { return err } if len(name) == 0 { return errors.New("couldn't read name from server") } var room int - if err := c.client.CallContext(c.GetContext(), &room, Namespace+"_room"); err != nil { + if err := c.client.CallContext(c.GetContext(), &room, server_api.Namespace+"_room"); err != nil { return err } if room < 2 { @@ -110,7 +113,7 @@ func NewExecutionClient(config rpcclient.ClientConfigFetcher, stack *node.Node) func (c *ExecutionClient) CreateExecutionRun(wasmModuleRoot common.Hash, input *validator.ValidationInput) containers.PromiseInterface[validator.ExecutionRun] { return stopwaiter.LaunchPromiseThread[validator.ExecutionRun](c, func(ctx context.Context) (validator.ExecutionRun, error) { var res uint64 - err := c.client.CallContext(ctx, &res, Namespace+"_createExecutionRun", wasmModuleRoot, ValidationInputToJson(input)) + err := c.client.CallContext(ctx, &res, server_api.Namespace+"_createExecutionRun", wasmModuleRoot, ValidationInputToJson(input)) if err != nil { return nil, err } @@ -132,7 +135,7 @@ type ExecutionClientRun struct { func (c *ExecutionClient) LatestWasmModuleRoot() containers.PromiseInterface[common.Hash] { return stopwaiter.LaunchPromiseThread[common.Hash](c, func(ctx context.Context) (common.Hash, error) { var res common.Hash - err := c.client.CallContext(ctx, &res, Namespace+"_latestWasmModuleRoot") + err := c.client.CallContext(ctx, &res, server_api.Namespace+"_latestWasmModuleRoot") if err != nil { return common.Hash{}, err } @@ -143,13 +146,13 @@ func (c *ExecutionClient) LatestWasmModuleRoot() containers.PromiseInterface[com func (c *ExecutionClient) WriteToFile(input *validator.ValidationInput, expOut validator.GoGlobalState, moduleRoot common.Hash) containers.PromiseInterface[struct{}] { jsonInput := ValidationInputToJson(input) return stopwaiter.LaunchPromiseThread[struct{}](c, func(ctx context.Context) (struct{}, error) { - err := c.client.CallContext(ctx, nil, Namespace+"_writeToFile", jsonInput, expOut, moduleRoot) + err := c.client.CallContext(ctx, nil, server_api.Namespace+"_writeToFile", jsonInput, expOut, moduleRoot) return struct{}{}, err }) } func (r *ExecutionClientRun) SendKeepAlive(ctx context.Context) time.Duration { - err := r.client.client.CallContext(ctx, nil, Namespace+"_execKeepAlive", r.id) + err := r.client.client.CallContext(ctx, nil, server_api.Namespace+"_execKeepAlive", r.id) if err != nil { log.Error("execution run keepalive failed", "err", err) } @@ -163,12 +166,12 @@ func (r *ExecutionClientRun) Start(ctx_in context.Context) { func (r *ExecutionClientRun) GetStepAt(pos uint64) containers.PromiseInterface[*validator.MachineStepResult] { return stopwaiter.LaunchPromiseThread[*validator.MachineStepResult](r, func(ctx context.Context) (*validator.MachineStepResult, error) { - var resJson MachineStepResultJson - err := r.client.client.CallContext(ctx, &resJson, Namespace+"_getStepAt", r.id, pos) + var resJson server_api.MachineStepResultJson + err := r.client.client.CallContext(ctx, &resJson, server_api.Namespace+"_getStepAt", r.id, pos) if err != nil { return nil, err } - res, err := MachineStepResultFromJson(&resJson) + res, err := server_api.MachineStepResultFromJson(&resJson) if err != nil { return nil, err } @@ -179,7 +182,7 @@ func (r *ExecutionClientRun) GetStepAt(pos uint64) containers.PromiseInterface[* func (r *ExecutionClientRun) GetProofAt(pos uint64) containers.PromiseInterface[[]byte] { return stopwaiter.LaunchPromiseThread[[]byte](r, func(ctx context.Context) ([]byte, error) { var resString string - err := r.client.client.CallContext(ctx, &resString, Namespace+"_getProofAt", r.id, pos) + err := r.client.client.CallContext(ctx, &resString, server_api.Namespace+"_getProofAt", r.id, pos) if err != nil { return nil, err } @@ -193,7 +196,7 @@ func (r *ExecutionClientRun) GetLastStep() containers.PromiseInterface[*validato func (r *ExecutionClientRun) PrepareRange(start, end uint64) containers.PromiseInterface[struct{}] { return stopwaiter.LaunchPromiseThread[struct{}](r, func(ctx context.Context) (struct{}, error) { - err := r.client.client.CallContext(ctx, nil, Namespace+"_prepareRange", r.id, start, end) + err := r.client.client.CallContext(ctx, nil, server_api.Namespace+"_prepareRange", r.id, start, end) if err != nil && ctx.Err() == nil { log.Warn("prepare execution got error", "err", err) } @@ -204,9 +207,29 @@ func (r *ExecutionClientRun) PrepareRange(start, end uint64) containers.PromiseI func (r *ExecutionClientRun) Close() { r.StopOnly() r.LaunchUntrackedThread(func() { - err := r.client.client.CallContext(r.GetParentContext(), nil, Namespace+"_closeExec", r.id) + err := r.client.client.CallContext(r.GetParentContext(), nil, server_api.Namespace+"_closeExec", r.id) if err != nil { log.Warn("closing execution client run got error", "err", err, "client", r.client.Name(), "id", r.id) } }) } + +func ValidationInputToJson(entry *validator.ValidationInput) *server_api.InputJSON { + jsonPreimagesMap := make(map[arbutil.PreimageType]*jsonapi.PreimagesMapJson) + for ty, preimages := range entry.Preimages { + jsonPreimagesMap[ty] = jsonapi.NewPreimagesMapJson(preimages) + } + res := &server_api.InputJSON{ + Id: entry.Id, + HasDelayedMsg: entry.HasDelayedMsg, + DelayedMsgNr: entry.DelayedMsgNr, + DelayedMsgB64: base64.StdEncoding.EncodeToString(entry.DelayedMsg), + StartState: entry.StartState, + PreimagesB64: jsonPreimagesMap, + } + for _, binfo := range entry.BatchInfo { + encData := base64.StdEncoding.EncodeToString(binfo.Data) + res.BatchInfo = append(res.BatchInfo, server_api.BatchInfoJson{Number: binfo.Number, DataB64: encData}) + } + return res +} diff --git a/validator/server_api/json.go b/validator/server_api/json.go index c1e4729571..e1729b53aa 100644 --- a/validator/server_api/json.go +++ b/validator/server_api/json.go @@ -4,65 +4,17 @@ package server_api import ( - "encoding/base64" + "fmt" "github.com/ethereum/go-ethereum/common" "github.com/offchainlabs/nitro/arbutil" + "github.com/offchainlabs/nitro/pubsub" "github.com/offchainlabs/nitro/util/jsonapi" "github.com/offchainlabs/nitro/validator" - "github.com/offchainlabs/nitro/validator/server_api/validation" + "github.com/spf13/pflag" ) -func ValidationInputToJson(entry *validator.ValidationInput) *validation.InputJSON { - jsonPreimagesMap := make(map[arbutil.PreimageType]*jsonapi.PreimagesMapJson) - for ty, preimages := range entry.Preimages { - jsonPreimagesMap[ty] = jsonapi.NewPreimagesMapJson(preimages) - } - res := &validation.InputJSON{ - Id: entry.Id, - HasDelayedMsg: entry.HasDelayedMsg, - DelayedMsgNr: entry.DelayedMsgNr, - DelayedMsgB64: base64.StdEncoding.EncodeToString(entry.DelayedMsg), - StartState: entry.StartState, - PreimagesB64: jsonPreimagesMap, - } - for _, binfo := range entry.BatchInfo { - encData := base64.StdEncoding.EncodeToString(binfo.Data) - res.BatchInfo = append(res.BatchInfo, validation.BatchInfoJson{Number: binfo.Number, DataB64: encData}) - } - return res -} - -func ValidationInputFromJson(entry *validation.InputJSON) (*validator.ValidationInput, error) { - preimages := make(map[arbutil.PreimageType]map[common.Hash][]byte) - for ty, jsonPreimages := range entry.PreimagesB64 { - preimages[ty] = jsonPreimages.Map - } - valInput := &validator.ValidationInput{ - Id: entry.Id, - HasDelayedMsg: entry.HasDelayedMsg, - DelayedMsgNr: entry.DelayedMsgNr, - StartState: entry.StartState, - Preimages: preimages, - } - delayed, err := base64.StdEncoding.DecodeString(entry.DelayedMsgB64) - if err != nil { - return nil, err - } - valInput.DelayedMsg = delayed - for _, binfo := range entry.BatchInfo { - data, err := base64.StdEncoding.DecodeString(binfo.DataB64) - if err != nil { - return nil, err - } - decInfo := validator.BatchInfo{ - Number: binfo.Number, - Data: data, - } - valInput.BatchInfo = append(valInput.BatchInfo, decInfo) - } - return valInput, nil -} +const Namespace string = "validation" type MachineStepResultJson struct { Hash common.Hash @@ -89,3 +41,51 @@ func MachineStepResultFromJson(resultJson *MachineStepResultJson) (*validator.Ma GlobalState: resultJson.GlobalState, }, nil } + +func RedisStreamForRoot(moduleRoot common.Hash) string { + return fmt.Sprintf("stream:%s", moduleRoot.Hex()) +} + +type Request struct { + Input *InputJSON + ModuleRoot common.Hash +} + +type InputJSON struct { + Id uint64 + HasDelayedMsg bool + DelayedMsgNr uint64 + PreimagesB64 map[arbutil.PreimageType]*jsonapi.PreimagesMapJson + BatchInfo []BatchInfoJson + DelayedMsgB64 string + StartState validator.GoGlobalState +} + +type BatchInfoJson struct { + Number uint64 + DataB64 string +} + +type RedisValidationServerConfig struct { + RedisURL string `koanf:"redis-url"` + ConsumerConfig pubsub.ConsumerConfig `koanf:"consumer-config"` + // Supported wasm module roots. + ModuleRoots []string `koanf:"module-roots"` +} + +var DefaultRedisValidationServerConfig = RedisValidationServerConfig{ + RedisURL: "", + ConsumerConfig: pubsub.DefaultConsumerConfig, + ModuleRoots: []string{}, +} + +var TestRedisValidationServerConfig = RedisValidationServerConfig{ + RedisURL: "", + ConsumerConfig: pubsub.TestConsumerConfig, + ModuleRoots: []string{}, +} + +func RedisValidationServerConfigAddOptions(prefix string, f *pflag.FlagSet) { + pubsub.ConsumerConfigAddOptions(prefix+".consumer-config", f) + f.StringSlice(prefix+".module-roots", nil, "Supported module root hashes") +} diff --git a/validator/server_api/validation/validation.go b/validator/server_api/validation/validation.go deleted file mode 100644 index 08d92085d6..0000000000 --- a/validator/server_api/validation/validation.go +++ /dev/null @@ -1,56 +0,0 @@ -// Package validation is introduced to avoid cyclic depenency between validation -// client and validation api. -package validation - -import ( - "github.com/ethereum/go-ethereum/common" - "github.com/offchainlabs/nitro/arbutil" - "github.com/offchainlabs/nitro/pubsub" - "github.com/offchainlabs/nitro/util/jsonapi" - "github.com/offchainlabs/nitro/validator" - "github.com/spf13/pflag" -) - -type Request struct { - Input *InputJSON - ModuleRoot common.Hash -} - -type InputJSON struct { - Id uint64 - HasDelayedMsg bool - DelayedMsgNr uint64 - PreimagesB64 map[arbutil.PreimageType]*jsonapi.PreimagesMapJson - BatchInfo []BatchInfoJson - DelayedMsgB64 string - StartState validator.GoGlobalState -} - -type BatchInfoJson struct { - Number uint64 - DataB64 string -} - -type RedisValidationServerConfig struct { - RedisURL string `koanf:"redis-url"` - ConsumerConfig pubsub.ConsumerConfig `koanf:"consumer-config"` - // Supported wasm module roots. - ModuleRoots []string `koanf:"module-roots"` -} - -var DefaultRedisValidationServerConfig = RedisValidationServerConfig{ - RedisURL: "", - ConsumerConfig: pubsub.DefaultConsumerConfig, - ModuleRoots: []string{}, -} - -var TestRedisValidationServerConfig = RedisValidationServerConfig{ - RedisURL: "", - ConsumerConfig: pubsub.TestConsumerConfig, - ModuleRoots: []string{}, -} - -func RedisValidationServerConfigAddOptions(prefix string, f *pflag.FlagSet) { - pubsub.ConsumerConfigAddOptions(prefix+".consumer-config", f) - f.StringSlice(prefix+".module-roots", nil, "Supported module root hashes") -} diff --git a/validator/server_arb/validator_spawner.go b/validator/server_arb/validator_spawner.go index a20a8d0e27..bc607d1088 100644 --- a/validator/server_arb/validator_spawner.go +++ b/validator/server_arb/validator_spawner.go @@ -17,7 +17,7 @@ import ( "github.com/offchainlabs/nitro/util/containers" "github.com/offchainlabs/nitro/util/stopwaiter" "github.com/offchainlabs/nitro/validator" - "github.com/offchainlabs/nitro/validator/server_api/validation" + "github.com/offchainlabs/nitro/validator/server_api" "github.com/offchainlabs/nitro/validator/server_common" "github.com/ethereum/go-ethereum/common" @@ -32,7 +32,7 @@ type ArbitratorSpawnerConfig struct { OutputPath string `koanf:"output-path" reload:"hot"` Execution MachineCacheConfig `koanf:"execution" reload:"hot"` // hot reloading for new executions only ExecutionRunTimeout time.Duration `koanf:"execution-run-timeout" reload:"hot"` - RedisValidationServerConfig validation.RedisValidationServerConfig `koanf:"redis-validation-server-config"` + RedisValidationServerConfig server_api.RedisValidationServerConfig `koanf:"redis-validation-server-config"` } type ArbitratorSpawnerConfigFecher func() *ArbitratorSpawnerConfig @@ -42,7 +42,7 @@ var DefaultArbitratorSpawnerConfig = ArbitratorSpawnerConfig{ OutputPath: "./target/output", Execution: DefaultMachineCacheConfig, ExecutionRunTimeout: time.Minute * 15, - RedisValidationServerConfig: validation.DefaultRedisValidationServerConfig, + RedisValidationServerConfig: server_api.DefaultRedisValidationServerConfig, } func ArbitratorSpawnerConfigAddOptions(prefix string, f *flag.FlagSet) { @@ -50,7 +50,7 @@ func ArbitratorSpawnerConfigAddOptions(prefix string, f *flag.FlagSet) { f.Duration(prefix+".execution-run-timeout", DefaultArbitratorSpawnerConfig.ExecutionRunTimeout, "timeout before discarding execution run") f.String(prefix+".output-path", DefaultArbitratorSpawnerConfig.OutputPath, "path to write machines to") MachineCacheConfigConfigAddOptions(prefix+".execution", f) - validation.RedisValidationServerConfigAddOptions(prefix+".redis-validation-server-config", f) + server_api.RedisValidationServerConfigAddOptions(prefix+".redis-validation-server-config", f) } func DefaultArbitratorSpawnerConfigFetcher() *ArbitratorSpawnerConfig { diff --git a/validator/server_api/redisconsumer.go b/validator/valnode/redisconsumer.go similarity index 90% rename from validator/server_api/redisconsumer.go rename to validator/valnode/redisconsumer.go index d87914380e..d90868fb9e 100644 --- a/validator/server_api/redisconsumer.go +++ b/validator/valnode/redisconsumer.go @@ -1,4 +1,4 @@ -package server_api +package valnode import ( "context" @@ -11,7 +11,7 @@ import ( "github.com/offchainlabs/nitro/util/redisutil" "github.com/offchainlabs/nitro/util/stopwaiter" "github.com/offchainlabs/nitro/validator" - "github.com/offchainlabs/nitro/validator/server_api/validation" + "github.com/offchainlabs/nitro/validator/server_api" ) // RedisValidationServer implements consumer for the requests originated from @@ -24,7 +24,7 @@ type RedisValidationServer struct { consumers map[common.Hash]*pubsub.Consumer[*validator.ValidationInput, validator.GoGlobalState] } -func NewRedisValidationServer(cfg *validation.RedisValidationServerConfig, spawner validator.ValidationSpawner) (*RedisValidationServer, error) { +func NewRedisValidationServer(cfg *server_api.RedisValidationServerConfig, spawner validator.ValidationSpawner) (*RedisValidationServer, error) { if cfg.RedisURL == "" { return nil, fmt.Errorf("redis url cannot be empty") } @@ -35,7 +35,7 @@ func NewRedisValidationServer(cfg *validation.RedisValidationServerConfig, spawn consumers := make(map[common.Hash]*pubsub.Consumer[*validator.ValidationInput, validator.GoGlobalState]) for _, hash := range cfg.ModuleRoots { mr := common.HexToHash(hash) - c, err := pubsub.NewConsumer[*validator.ValidationInput, validator.GoGlobalState](redisClient, RedisStreamForRoot(mr), &cfg.ConsumerConfig) + c, err := pubsub.NewConsumer[*validator.ValidationInput, validator.GoGlobalState](redisClient, server_api.RedisStreamForRoot(mr), &cfg.ConsumerConfig) if err != nil { return nil, fmt.Errorf("creating consumer for validation: %w", err) } diff --git a/validator/server_api/validation_api.go b/validator/valnode/validation_api.go similarity index 76% rename from validator/server_api/validation_api.go rename to validator/valnode/validation_api.go index 076e1ef79c..432e5eedd9 100644 --- a/validator/server_api/validation_api.go +++ b/validator/valnode/validation_api.go @@ -1,4 +1,4 @@ -package server_api +package valnode import ( "context" @@ -10,14 +10,13 @@ import ( "github.com/ethereum/go-ethereum/common" + "github.com/offchainlabs/nitro/arbutil" "github.com/offchainlabs/nitro/util/stopwaiter" "github.com/offchainlabs/nitro/validator" - "github.com/offchainlabs/nitro/validator/server_api/validation" + "github.com/offchainlabs/nitro/validator/server_api" "github.com/offchainlabs/nitro/validator/server_arb" ) -const Namespace string = "validation" - type ValidationServerAPI struct { spawner validator.ValidationSpawner } @@ -30,7 +29,7 @@ func (a *ValidationServerAPI) Room() int { return a.spawner.Room() } -func (a *ValidationServerAPI) Validate(ctx context.Context, entry *validation.InputJSON, moduleRoot common.Hash) (validator.GoGlobalState, error) { +func (a *ValidationServerAPI) Validate(ctx context.Context, entry *server_api.InputJSON, moduleRoot common.Hash) (validator.GoGlobalState, error) { valInput, err := ValidationInputFromJson(entry) if err != nil { return validator.GoGlobalState{}, err @@ -70,7 +69,7 @@ func NewExecutionServerAPI(valSpawner validator.ValidationSpawner, execution val } } -func (a *ExecServerAPI) CreateExecutionRun(ctx context.Context, wasmModuleRoot common.Hash, jsonInput *validation.InputJSON) (uint64, error) { +func (a *ExecServerAPI) CreateExecutionRun(ctx context.Context, wasmModuleRoot common.Hash, jsonInput *server_api.InputJSON) (uint64, error) { input, err := ValidationInputFromJson(jsonInput) if err != nil { return 0, err @@ -108,7 +107,7 @@ func (a *ExecServerAPI) Start(ctx_in context.Context) { a.CallIteratively(a.removeOldRuns) } -func (a *ExecServerAPI) WriteToFile(ctx context.Context, jsonInput *validation.InputJSON, expOut validator.GoGlobalState, moduleRoot common.Hash) error { +func (a *ExecServerAPI) WriteToFile(ctx context.Context, jsonInput *server_api.InputJSON, expOut validator.GoGlobalState, moduleRoot common.Hash) error { input, err := ValidationInputFromJson(jsonInput) if err != nil { return err @@ -130,7 +129,7 @@ func (a *ExecServerAPI) getRun(id uint64) (validator.ExecutionRun, error) { return entry.run, nil } -func (a *ExecServerAPI) GetStepAt(ctx context.Context, execid uint64, position uint64) (*MachineStepResultJson, error) { +func (a *ExecServerAPI) GetStepAt(ctx context.Context, execid uint64, position uint64) (*server_api.MachineStepResultJson, error) { run, err := a.getRun(execid) if err != nil { return nil, err @@ -140,7 +139,7 @@ func (a *ExecServerAPI) GetStepAt(ctx context.Context, execid uint64, position u if err != nil { return nil, err } - return MachineStepResultToJson(res), nil + return server_api.MachineStepResultToJson(res), nil } func (a *ExecServerAPI) GetProofAt(ctx context.Context, execid uint64, position uint64) (string, error) { @@ -183,3 +182,34 @@ func (a *ExecServerAPI) CloseExec(execid uint64) { run.run.Close() delete(a.runs, execid) } + +func ValidationInputFromJson(entry *server_api.InputJSON) (*validator.ValidationInput, error) { + preimages := make(map[arbutil.PreimageType]map[common.Hash][]byte) + for ty, jsonPreimages := range entry.PreimagesB64 { + preimages[ty] = jsonPreimages.Map + } + valInput := &validator.ValidationInput{ + Id: entry.Id, + HasDelayedMsg: entry.HasDelayedMsg, + DelayedMsgNr: entry.DelayedMsgNr, + StartState: entry.StartState, + Preimages: preimages, + } + delayed, err := base64.StdEncoding.DecodeString(entry.DelayedMsgB64) + if err != nil { + return nil, err + } + valInput.DelayedMsg = delayed + for _, binfo := range entry.BatchInfo { + data, err := base64.StdEncoding.DecodeString(binfo.DataB64) + if err != nil { + return nil, err + } + decInfo := validator.BatchInfo{ + Number: binfo.Number, + Data: data, + } + valInput.BatchInfo = append(valInput.BatchInfo, decInfo) + } + return valInput, nil +} diff --git a/validator/valnode/valnode.go b/validator/valnode/valnode.go index e42acd8ae4..bbb680087a 100644 --- a/validator/valnode/valnode.go +++ b/validator/valnode/valnode.go @@ -77,7 +77,7 @@ type ValidationNode struct { arbSpawner *server_arb.ArbitratorSpawner jitSpawner *server_jit.JitSpawner - redisConsumer *server_api.RedisValidationServer + redisConsumer *RedisValidationServer } func EnsureValidationExposedViaAuthRPC(stackConf *node.Config) { @@ -106,7 +106,7 @@ func CreateValidationNode(configFetcher ValidationConfigFetcher, stack *node.Nod if err != nil { return nil, err } - var serverAPI *server_api.ExecServerAPI + var serverAPI *ExecServerAPI var jitSpawner *server_jit.JitSpawner if config.UseJit { jitConfigFetcher := func() *server_jit.JitSpawnerConfig { return &configFetcher().Jit } @@ -115,11 +115,11 @@ func CreateValidationNode(configFetcher ValidationConfigFetcher, stack *node.Nod if err != nil { return nil, err } - serverAPI = server_api.NewExecutionServerAPI(jitSpawner, arbSpawner, arbConfigFetcher) + serverAPI = NewExecutionServerAPI(jitSpawner, arbSpawner, arbConfigFetcher) } else { - serverAPI = server_api.NewExecutionServerAPI(arbSpawner, arbSpawner, arbConfigFetcher) + serverAPI = NewExecutionServerAPI(arbSpawner, arbSpawner, arbConfigFetcher) } - redisConsumer, err := server_api.NewRedisValidationServer(&arbConfigFetcher().RedisValidationServerConfig, arbSpawner) + redisConsumer, err := NewRedisValidationServer(&arbConfigFetcher().RedisValidationServerConfig, arbSpawner) if err != nil { log.Error("Creating new redis validation server", "error", err) } From 005f4410ada241c393d4cd143160ac72830d1ac1 Mon Sep 17 00:00:00 2001 From: Nodar Ambroladze Date: Sat, 20 Apr 2024 11:08:56 +0200 Subject: [PATCH 11/17] Fix TestChallengeManagerFullAsserterCorrect test --- staker/stateless_block_validator.go | 16 +++++++++------- system_tests/common_test.go | 4 ++++ system_tests/validation_mock_test.go | 10 ++++++---- validator/client/redisproducer.go | 2 +- validator/client/validation_client.go | 6 ++---- 5 files changed, 22 insertions(+), 16 deletions(-) diff --git a/staker/stateless_block_validator.go b/staker/stateless_block_validator.go index eaa2bfb133..8386d0b80c 100644 --- a/staker/stateless_block_validator.go +++ b/staker/stateless_block_validator.go @@ -195,10 +195,12 @@ func NewStatelessBlockValidator( stack *node.Node, ) (*StatelessBlockValidator, error) { var validationSpawners []validator.ValidationSpawner - redisValClient, err := validatorclient.NewRedisValidationClient(&config().RedisValidationClientConfig) - if err != nil { - log.Error("Creating redis validation client", "error", err) - } else { + if config().RedisValidationClientConfig.Enabled() { + redisValClient, err := validatorclient.NewRedisValidationClient(&config().RedisValidationClientConfig) + if err != nil { + return nil, fmt.Errorf("creating new redis validation client: %w", err) + // log.Error("Creating redis validation client, redis validator disabled", "error", err) + } validationSpawners = append(validationSpawners, redisValClient) } for _, serverConfig := range config().ValidationServerConfigs { @@ -427,17 +429,17 @@ func (v *StatelessBlockValidator) OverrideRecorder(t *testing.T, recorder execut func (v *StatelessBlockValidator) Start(ctx_in context.Context) error { for _, spawner := range v.validationSpawners { if err := spawner.Start(ctx_in); err != nil { - return err + return fmt.Errorf("starting validation spawner: %w", err) } } if err := v.execSpawner.Start(ctx_in); err != nil { - return err + return fmt.Errorf("starting execution spawner: %w", err) } if v.config.PendingUpgradeModuleRoot != "" { if v.config.PendingUpgradeModuleRoot == "latest" { latest, err := v.execSpawner.LatestWasmModuleRoot().Await(ctx_in) if err != nil { - return err + return fmt.Errorf("getting latest wasm module root: %w", err) } v.pendingWasmModuleRoot = latest } else { diff --git a/system_tests/common_test.go b/system_tests/common_test.go index 54e40219f3..ebf903cfa8 100644 --- a/system_tests/common_test.go +++ b/system_tests/common_test.go @@ -578,6 +578,10 @@ func StaticFetcherFrom[T any](t *testing.T, config *T) func() *T { func configByValidationNode(clientConfig *arbnode.Config, valStack *node.Node) { clientConfig.BlockValidator.ExecutionServerConfig.URL = valStack.WSEndpoint() clientConfig.BlockValidator.ExecutionServerConfig.JWTSecret = "" + if len(clientConfig.BlockValidator.ValidationServerConfigs) != 0 { + clientConfig.BlockValidator.ValidationServerConfigs[0].URL = valStack.WSEndpoint() + clientConfig.BlockValidator.ValidationServerConfigs[0].JWTSecret = "" + } } func currentRootModule(t *testing.T) common.Hash { diff --git a/system_tests/validation_mock_test.go b/system_tests/validation_mock_test.go index 2deb99b09a..788dfc5d7a 100644 --- a/system_tests/validation_mock_test.go +++ b/system_tests/validation_mock_test.go @@ -67,10 +67,12 @@ func (s *mockSpawner) Launch(entry *validator.ValidationInput, moduleRoot common var mockWasmModuleRoot common.Hash = common.HexToHash("0xa5a5a5") -func (s *mockSpawner) Start(context.Context) error { return nil } -func (s *mockSpawner) Stop() {} -func (s *mockSpawner) Name() string { return "mock" } -func (s *mockSpawner) Room() int { return 4 } +func (s *mockSpawner) Start(context.Context) error { + return nil +} +func (s *mockSpawner) Stop() {} +func (s *mockSpawner) Name() string { return "mock" } +func (s *mockSpawner) Room() int { return 4 } func (s *mockSpawner) CreateExecutionRun(wasmModuleRoot common.Hash, input *validator.ValidationInput) containers.PromiseInterface[validator.ExecutionRun] { s.ExecSpawned = append(s.ExecSpawned, input.Id) diff --git a/validator/client/redisproducer.go b/validator/client/redisproducer.go index cfe738f649..a2a9d28eb5 100644 --- a/validator/client/redisproducer.go +++ b/validator/client/redisproducer.go @@ -26,7 +26,7 @@ type RedisValidationClientConfig struct { } func (c RedisValidationClientConfig) Enabled() bool { - return len(c.ModuleRoots) > 0 + return c.RedisURL != "" } var DefaultRedisValidationClientConfig = RedisValidationClientConfig{ diff --git a/validator/client/validation_client.go b/validator/client/validation_client.go index ffa6ca9bd6..24e51230d6 100644 --- a/validator/client/validation_client.go +++ b/validator/client/validation_client.go @@ -51,10 +51,8 @@ func (c *ValidationClient) Launch(entry *validator.ValidationInput, moduleRoot c func (c *ValidationClient) Start(ctx_in context.Context) error { c.StopWaiter.Start(ctx_in, c) ctx := c.GetContext() - if c.client != nil { - if err := c.client.Start(ctx); err != nil { - return err - } + if err := c.client.Start(ctx); err != nil { + return err } var name string if err := c.client.CallContext(ctx, &name, server_api.Namespace+"_name"); err != nil { From a0268fe9e196b148705d7abf0484868154046c92 Mon Sep 17 00:00:00 2001 From: Nodar Ambroladze Date: Mon, 22 Apr 2024 16:06:28 +0200 Subject: [PATCH 12/17] Add config validation --- staker/block_validator.go | 6 ++++++ staker/stateless_block_validator.go | 13 ++++++------- validator/client/redisproducer.go | 29 ++++++++++++++++++++++++++--- 3 files changed, 38 insertions(+), 10 deletions(-) diff --git a/staker/block_validator.go b/staker/block_validator.go index cd89ccf650..806e5d44a1 100644 --- a/staker/block_validator.go +++ b/staker/block_validator.go @@ -134,6 +134,9 @@ func (c *BlockValidatorConfig) Validate() error { if err := c.ExecutionServerConfig.Validate(); err != nil { return fmt.Errorf("validating execution server config: %w", err) } + if err := c.RedisValidationClientConfig.Validate(); err != nil { + return fmt.Errorf("validating redis validation client configuration: %w", err) + } return nil } @@ -146,6 +149,8 @@ type BlockValidatorConfigFetcher func() *BlockValidatorConfig func BlockValidatorConfigAddOptions(prefix string, f *pflag.FlagSet) { f.Bool(prefix+".enable", DefaultBlockValidatorConfig.Enable, "enable block-by-block validation") rpcclient.RPCClientAddOptions(prefix+".validation-server", f, &DefaultBlockValidatorConfig.ValidationServer) + rpcclient.RPCClientAddOptions(prefix+".execution-server-config", f, &DefaultBlockValidatorConfig.ExecutionServerConfig) + validatorclient.RedisValidationClientConfigAddOptions(prefix+"redis-validation-client-config", f) f.String(prefix+".validation-server-configs-list", DefaultBlockValidatorConfig.ValidationServerConfigsList, "array of validation rpc configs given as a json string. time duration should be supplied in number indicating nanoseconds") f.Duration(prefix+".validation-poll", DefaultBlockValidatorConfig.ValidationPoll, "poll time to check validations") f.Uint64(prefix+".forward-blocks", DefaultBlockValidatorConfig.ForwardBlocks, "prepare entries for up to that many blocks ahead of validation (small footprint)") @@ -165,6 +170,7 @@ var DefaultBlockValidatorConfig = BlockValidatorConfig{ Enable: false, ValidationServerConfigsList: "default", ValidationServer: rpcclient.DefaultClientConfig, + ExecutionServerConfig: rpcclient.DefaultClientConfig, ValidationPoll: time.Second, ForwardBlocks: 1024, PrerecordedBlocks: uint64(2 * runtime.NumCPU()), diff --git a/staker/stateless_block_validator.go b/staker/stateless_block_validator.go index 8386d0b80c..74b87f0291 100644 --- a/staker/stateless_block_validator.go +++ b/staker/stateless_block_validator.go @@ -208,7 +208,10 @@ func NewStatelessBlockValidator( validationSpawners = append(validationSpawners, validatorclient.NewValidationClient(valConfFetcher, stack)) } - validator := &StatelessBlockValidator{ + valConfFetcher := func() *rpcclient.ClientConfig { + return &config().ExecutionServerConfig + } + return &StatelessBlockValidator{ config: config(), recorder: recorder, validationSpawners: validationSpawners, @@ -218,12 +221,8 @@ func NewStatelessBlockValidator( db: arbdb, daService: das, blobReader: blobReader, - } - valConfFetcher := func() *rpcclient.ClientConfig { - return &config().ExecutionServerConfig - } - validator.execSpawner = validatorclient.NewExecutionClient(valConfFetcher, stack) - return validator, nil + execSpawner: validatorclient.NewExecutionClient(valConfFetcher, stack), + }, nil } func (v *StatelessBlockValidator) GetModuleRootsToValidate() []common.Hash { diff --git a/validator/client/redisproducer.go b/validator/client/redisproducer.go index a2a9d28eb5..50e58c4e66 100644 --- a/validator/client/redisproducer.go +++ b/validator/client/redisproducer.go @@ -6,6 +6,7 @@ import ( "sync/atomic" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/log" "github.com/offchainlabs/nitro/pubsub" "github.com/offchainlabs/nitro/util/containers" "github.com/offchainlabs/nitro/util/redisutil" @@ -23,12 +24,35 @@ type RedisValidationClientConfig struct { ProducerConfig pubsub.ProducerConfig `koanf:"producer-config"` // Supported wasm module roots, when the list is empty this is disabled. ModuleRoots []string `koanf:"module-roots"` + moduleRoots []common.Hash } func (c RedisValidationClientConfig) Enabled() bool { return c.RedisURL != "" } +func (c *RedisValidationClientConfig) Validate() error { + m := make(map[string]bool) + // Add all moduleRoot hashes in case Validate is called twice so that we + // don't add duplicate moduleRoots again. + for _, mr := range c.moduleRoots { + m[mr.Hex()] = true + } + for _, mr := range c.ModuleRoots { + if _, exists := m[mr]; exists { + log.Warn("Duplicate module root", "hash", mr) + continue + } + h := common.HexToHash(mr) + if h == (common.Hash{}) { + return fmt.Errorf("invalid module root hash: %q", mr) + } + m[mr] = true + c.moduleRoots = append(c.moduleRoots, h) + } + return nil +} + var DefaultRedisValidationClientConfig = RedisValidationClientConfig{ Name: "redis validation client", Room: 2, @@ -72,11 +96,10 @@ func NewRedisValidationClient(cfg *RedisValidationClientConfig) (*RedisValidatio if err != nil { return nil, err } - if len(cfg.ModuleRoots) == 0 { + if len(cfg.moduleRoots) == 0 { return nil, fmt.Errorf("moduleRoots must be specified to enable redis streams") } - for _, hash := range cfg.ModuleRoots { - mr := common.HexToHash(hash) + for _, mr := range cfg.moduleRoots { p, err := pubsub.NewProducer[*validator.ValidationInput, validator.GoGlobalState]( redisClient, server_api.RedisStreamForRoot(mr), &cfg.ProducerConfig) if err != nil { From 4e837508ebe925cfe5d7d33d07f1c41b2920efef Mon Sep 17 00:00:00 2001 From: Nodar Ambroladze Date: Mon, 22 Apr 2024 18:49:21 +0200 Subject: [PATCH 13/17] Fix config defaults --- pubsub/producer.go | 1 + staker/block_validator.go | 28 +++++++++++++++------------- validator/client/redisproducer.go | 2 ++ 3 files changed, 18 insertions(+), 13 deletions(-) diff --git a/pubsub/producer.go b/pubsub/producer.go index b00eec7f64..074670ca0f 100644 --- a/pubsub/producer.go +++ b/pubsub/producer.go @@ -78,6 +78,7 @@ var TestProducerConfig = ProducerConfig{ func ProducerAddConfigAddOptions(prefix string, f *pflag.FlagSet) { f.Bool(prefix+".enable-reproduce", DefaultProducerConfig.EnableReproduce, "when enabled, messages with dead consumer will be re-inserted into the stream") f.Duration(prefix+".check-pending-interval", DefaultProducerConfig.CheckPendingInterval, "interval in which producer checks pending messages whether consumer processing them is inactive") + f.Duration(prefix+".check-result-interval", DefaultProducerConfig.CheckResultInterval, "interval in which producer checks pending messages whether consumer processing them is inactive") f.Duration(prefix+".keepalive-timeout", DefaultProducerConfig.KeepAliveTimeout, "timeout after which consumer is considered inactive if heartbeat wasn't performed") } diff --git a/staker/block_validator.go b/staker/block_validator.go index 806e5d44a1..b66fcea44c 100644 --- a/staker/block_validator.go +++ b/staker/block_validator.go @@ -150,7 +150,7 @@ func BlockValidatorConfigAddOptions(prefix string, f *pflag.FlagSet) { f.Bool(prefix+".enable", DefaultBlockValidatorConfig.Enable, "enable block-by-block validation") rpcclient.RPCClientAddOptions(prefix+".validation-server", f, &DefaultBlockValidatorConfig.ValidationServer) rpcclient.RPCClientAddOptions(prefix+".execution-server-config", f, &DefaultBlockValidatorConfig.ExecutionServerConfig) - validatorclient.RedisValidationClientConfigAddOptions(prefix+"redis-validation-client-config", f) + validatorclient.RedisValidationClientConfigAddOptions(prefix+".redis-validation-client-config", f) f.String(prefix+".validation-server-configs-list", DefaultBlockValidatorConfig.ValidationServerConfigsList, "array of validation rpc configs given as a json string. time duration should be supplied in number indicating nanoseconds") f.Duration(prefix+".validation-poll", DefaultBlockValidatorConfig.ValidationPoll, "poll time to check validations") f.Uint64(prefix+".forward-blocks", DefaultBlockValidatorConfig.ForwardBlocks, "prepare entries for up to that many blocks ahead of validation (small footprint)") @@ -171,6 +171,7 @@ var DefaultBlockValidatorConfig = BlockValidatorConfig{ ValidationServerConfigsList: "default", ValidationServer: rpcclient.DefaultClientConfig, ExecutionServerConfig: rpcclient.DefaultClientConfig, + RedisValidationClientConfig: validatorclient.DefaultRedisValidationClientConfig, ValidationPoll: time.Second, ForwardBlocks: 1024, PrerecordedBlocks: uint64(2 * runtime.NumCPU()), @@ -182,18 +183,19 @@ var DefaultBlockValidatorConfig = BlockValidatorConfig{ } var TestBlockValidatorConfig = BlockValidatorConfig{ - Enable: false, - ValidationServer: rpcclient.TestClientConfig, - ValidationServerConfigs: []rpcclient.ClientConfig{rpcclient.TestClientConfig}, - ExecutionServerConfig: rpcclient.TestClientConfig, - ValidationPoll: 100 * time.Millisecond, - ForwardBlocks: 128, - PrerecordedBlocks: uint64(2 * runtime.NumCPU()), - CurrentModuleRoot: "latest", - PendingUpgradeModuleRoot: "latest", - FailureIsFatal: true, - Dangerous: DefaultBlockValidatorDangerousConfig, - MemoryFreeLimit: "default", + Enable: false, + ValidationServer: rpcclient.TestClientConfig, + ValidationServerConfigs: []rpcclient.ClientConfig{rpcclient.TestClientConfig}, + RedisValidationClientConfig: validatorclient.TestRedisValidationClientConfig, + ExecutionServerConfig: rpcclient.TestClientConfig, + ValidationPoll: 100 * time.Millisecond, + ForwardBlocks: 128, + PrerecordedBlocks: uint64(2 * runtime.NumCPU()), + CurrentModuleRoot: "latest", + PendingUpgradeModuleRoot: "latest", + FailureIsFatal: true, + Dangerous: DefaultBlockValidatorDangerousConfig, + MemoryFreeLimit: "default", } var DefaultBlockValidatorDangerousConfig = BlockValidatorDangerousConfig{ diff --git a/validator/client/redisproducer.go b/validator/client/redisproducer.go index 50e58c4e66..bfc083daf9 100644 --- a/validator/client/redisproducer.go +++ b/validator/client/redisproducer.go @@ -58,6 +58,7 @@ var DefaultRedisValidationClientConfig = RedisValidationClientConfig{ Room: 2, RedisURL: "", ProducerConfig: pubsub.DefaultProducerConfig, + ModuleRoots: []string{}, } var TestRedisValidationClientConfig = RedisValidationClientConfig{ @@ -65,6 +66,7 @@ var TestRedisValidationClientConfig = RedisValidationClientConfig{ Room: 2, RedisURL: "", ProducerConfig: pubsub.TestProducerConfig, + ModuleRoots: []string{}, } func RedisValidationClientConfigAddOptions(prefix string, f *pflag.FlagSet) { From 123023e4e8947d6b03de46827e08bcdfc6aa0802 Mon Sep 17 00:00:00 2001 From: Nodar Ambroladze Date: Mon, 22 Apr 2024 21:15:16 +0200 Subject: [PATCH 14/17] drop moduleRoots from config and initialize from block_validator instead --- staker/block_validator.go | 6 +-- staker/stateless_block_validator.go | 14 +++++- system_tests/block_validator_test.go | 1 - validator/client/redisproducer.go | 66 ++++++++++------------------ 4 files changed, 40 insertions(+), 47 deletions(-) diff --git a/staker/block_validator.go b/staker/block_validator.go index b66fcea44c..1a601db8a9 100644 --- a/staker/block_validator.go +++ b/staker/block_validator.go @@ -134,9 +134,6 @@ func (c *BlockValidatorConfig) Validate() error { if err := c.ExecutionServerConfig.Validate(); err != nil { return fmt.Errorf("validating execution server config: %w", err) } - if err := c.RedisValidationClientConfig.Validate(); err != nil { - return fmt.Errorf("validating redis validation client configuration: %w", err) - } return nil } @@ -1068,6 +1065,9 @@ func (v *BlockValidator) Initialize(ctx context.Context) error { } } log.Info("BlockValidator initialized", "current", v.currentWasmModuleRoot, "pending", v.pendingWasmModuleRoot) + if err := v.StatelessBlockValidator.Initialize([]common.Hash{v.currentWasmModuleRoot, v.pendingWasmModuleRoot}); err != nil { + return fmt.Errorf("initializing block validator with module roots: %w", err) + } return nil } diff --git a/staker/stateless_block_validator.go b/staker/stateless_block_validator.go index 74b87f0291..4f71e39545 100644 --- a/staker/stateless_block_validator.go +++ b/staker/stateless_block_validator.go @@ -199,7 +199,6 @@ func NewStatelessBlockValidator( redisValClient, err := validatorclient.NewRedisValidationClient(&config().RedisValidationClientConfig) if err != nil { return nil, fmt.Errorf("creating new redis validation client: %w", err) - // log.Error("Creating redis validation client, redis validator disabled", "error", err) } validationSpawners = append(validationSpawners, redisValClient) } @@ -225,6 +224,19 @@ func NewStatelessBlockValidator( }, nil } +func (v *StatelessBlockValidator) Initialize(moduleRoots []common.Hash) error { + if len(v.validationSpawners) == 0 { + return nil + } + // First spawner is always RedisValidationClient if RedisStreams are enabled. + if v, ok := v.validationSpawners[0].(*validatorclient.RedisValidationClient); ok { + if err := v.Initialize(moduleRoots); err != nil { + return fmt.Errorf("initializing redis validation client module roots: %w", err) + } + } + return nil +} + func (v *StatelessBlockValidator) GetModuleRootsToValidate() []common.Hash { v.moduleMutex.Lock() defer v.moduleMutex.Unlock() diff --git a/system_tests/block_validator_test.go b/system_tests/block_validator_test.go index ed8438eb78..a7c85bf5e4 100644 --- a/system_tests/block_validator_test.go +++ b/system_tests/block_validator_test.go @@ -74,7 +74,6 @@ func testBlockValidatorSimple(t *testing.T, dasModeString string, workloadLoops if useRedisStreams { redisURL = redisutil.CreateTestRedis(ctx, t) validatorConfig.BlockValidator.RedisValidationClientConfig = validatorclient.DefaultRedisValidationClientConfig - validatorConfig.BlockValidator.RedisValidationClientConfig.ModuleRoots = []string{currentRootModule(t).Hex()} validatorConfig.BlockValidator.RedisValidationClientConfig.RedisURL = redisURL validatorConfig.BlockValidator.ValidationServerConfigs = nil } diff --git a/validator/client/redisproducer.go b/validator/client/redisproducer.go index bfc083daf9..07569d51b6 100644 --- a/validator/client/redisproducer.go +++ b/validator/client/redisproducer.go @@ -7,6 +7,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/log" + "github.com/go-redis/redis/v8" "github.com/offchainlabs/nitro/pubsub" "github.com/offchainlabs/nitro/util/containers" "github.com/offchainlabs/nitro/util/redisutil" @@ -22,43 +23,17 @@ type RedisValidationClientConfig struct { Room int32 `koanf:"room"` RedisURL string `koanf:"redis-url"` ProducerConfig pubsub.ProducerConfig `koanf:"producer-config"` - // Supported wasm module roots, when the list is empty this is disabled. - ModuleRoots []string `koanf:"module-roots"` - moduleRoots []common.Hash } func (c RedisValidationClientConfig) Enabled() bool { return c.RedisURL != "" } -func (c *RedisValidationClientConfig) Validate() error { - m := make(map[string]bool) - // Add all moduleRoot hashes in case Validate is called twice so that we - // don't add duplicate moduleRoots again. - for _, mr := range c.moduleRoots { - m[mr.Hex()] = true - } - for _, mr := range c.ModuleRoots { - if _, exists := m[mr]; exists { - log.Warn("Duplicate module root", "hash", mr) - continue - } - h := common.HexToHash(mr) - if h == (common.Hash{}) { - return fmt.Errorf("invalid module root hash: %q", mr) - } - m[mr] = true - c.moduleRoots = append(c.moduleRoots, h) - } - return nil -} - var DefaultRedisValidationClientConfig = RedisValidationClientConfig{ Name: "redis validation client", Room: 2, RedisURL: "", ProducerConfig: pubsub.DefaultProducerConfig, - ModuleRoots: []string{}, } var TestRedisValidationClientConfig = RedisValidationClientConfig{ @@ -66,14 +41,12 @@ var TestRedisValidationClientConfig = RedisValidationClientConfig{ Room: 2, RedisURL: "", ProducerConfig: pubsub.TestProducerConfig, - ModuleRoots: []string{}, } func RedisValidationClientConfigAddOptions(prefix string, f *pflag.FlagSet) { f.String(prefix+".name", DefaultRedisValidationClientConfig.Name, "validation client name") f.Int32(prefix+".room", DefaultRedisValidationClientConfig.Room, "validation client room") pubsub.ProducerAddConfigAddOptions(prefix+".producer-config", f) - f.StringSlice(prefix+".module-roots", nil, "Supported module root hashes") } // RedisValidationClient implements validation client through redis streams. @@ -82,15 +55,12 @@ type RedisValidationClient struct { name string room int32 // producers stores moduleRoot to producer mapping. - producers map[common.Hash]*pubsub.Producer[*validator.ValidationInput, validator.GoGlobalState] + producers map[common.Hash]*pubsub.Producer[*validator.ValidationInput, validator.GoGlobalState] + producerConfig pubsub.ProducerConfig + redisClient redis.UniversalClient } func NewRedisValidationClient(cfg *RedisValidationClientConfig) (*RedisValidationClient, error) { - res := &RedisValidationClient{ - name: cfg.Name, - room: cfg.Room, - producers: make(map[common.Hash]*pubsub.Producer[*validator.ValidationInput, validator.GoGlobalState]), - } if cfg.RedisURL == "" { return nil, fmt.Errorf("redis url cannot be empty") } @@ -98,18 +68,30 @@ func NewRedisValidationClient(cfg *RedisValidationClientConfig) (*RedisValidatio if err != nil { return nil, err } - if len(cfg.moduleRoots) == 0 { - return nil, fmt.Errorf("moduleRoots must be specified to enable redis streams") - } - for _, mr := range cfg.moduleRoots { + return &RedisValidationClient{ + name: cfg.Name, + room: cfg.Room, + producers: make(map[common.Hash]*pubsub.Producer[*validator.ValidationInput, validator.GoGlobalState]), + producerConfig: cfg.ProducerConfig, + redisClient: redisClient, + }, nil +} + +func (c *RedisValidationClient) Initialize(moduleRoots []common.Hash) error { + for _, mr := range moduleRoots { + if _, exists := c.producers[mr]; exists { + log.Warn("Producer already existsw for module root", "hash", mr) + continue + } p, err := pubsub.NewProducer[*validator.ValidationInput, validator.GoGlobalState]( - redisClient, server_api.RedisStreamForRoot(mr), &cfg.ProducerConfig) + c.redisClient, server_api.RedisStreamForRoot(mr), &c.producerConfig) if err != nil { - return nil, fmt.Errorf("creating producer for validation: %w", err) + return fmt.Errorf("creating producer for validation: %w", err) } - res.producers[mr] = p + p.Start(c.GetContext()) + c.producers[mr] = p } - return res, nil + return nil } func (c *RedisValidationClient) Launch(entry *validator.ValidationInput, moduleRoot common.Hash) validator.ValidationRun { From 27edb42dc00ec8d3176dacaf94e332cd75d8f459 Mon Sep 17 00:00:00 2001 From: Nodar Ambroladze Date: Tue, 23 Apr 2024 11:18:33 +0200 Subject: [PATCH 15/17] Cast bytes to fixed size array instead of copying --- gethhook/geth-hook.go | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/gethhook/geth-hook.go b/gethhook/geth-hook.go index dcd1788710..08b96b384f 100644 --- a/gethhook/geth-hook.go +++ b/gethhook/geth-hook.go @@ -58,9 +58,7 @@ func init() { precompileErrors := make(map[[4]byte]abi.Error) for addr, precompile := range precompiles.Precompiles() { for _, errABI := range precompile.Precompile().GetErrorABIs() { - var id [4]byte - copy(id[:], errABI.ID[:4]) - precompileErrors[id] = errABI + precompileErrors[[4]byte(errABI.ID.Bytes())] = errABI } var wrapped vm.AdvancedPrecompile = ArbosPrecompileWrapper{precompile} vm.PrecompiledContractsArbitrum[addr] = wrapped From 9dfe3d179a2059881408caa2bdeb2d4fe17d98f0 Mon Sep 17 00:00:00 2001 From: Nodar Ambroladze Date: Tue, 23 Apr 2024 12:02:42 +0200 Subject: [PATCH 16/17] Factor out redisproducer and redisconumer --- staker/block_validator.go | 43 ++++++++++--------- staker/stateless_block_validator.go | 5 ++- system_tests/block_validator_test.go | 5 +-- system_tests/common_test.go | 3 +- .../{redisproducer.go => redis/producer.go} | 36 ++++++++-------- validator/server_api/json.go | 26 ----------- validator/server_arb/validator_spawner.go | 20 ++++----- .../{redisconsumer.go => redis/consumer.go} | 37 +++++++++++++--- validator/valnode/valnode.go | 12 +++--- 9 files changed, 95 insertions(+), 92 deletions(-) rename validator/client/{redisproducer.go => redis/producer.go} (73%) rename validator/valnode/{redisconsumer.go => redis/consumer.go} (64%) diff --git a/staker/block_validator.go b/staker/block_validator.go index 1a601db8a9..0cde4423c0 100644 --- a/staker/block_validator.go +++ b/staker/block_validator.go @@ -25,9 +25,8 @@ import ( "github.com/offchainlabs/nitro/util/rpcclient" "github.com/offchainlabs/nitro/util/stopwaiter" "github.com/offchainlabs/nitro/validator" + "github.com/offchainlabs/nitro/validator/client/redis" "github.com/spf13/pflag" - - validatorclient "github.com/offchainlabs/nitro/validator/client" ) var ( @@ -84,20 +83,20 @@ type BlockValidator struct { } type BlockValidatorConfig struct { - Enable bool `koanf:"enable"` - ValidationServer rpcclient.ClientConfig `koanf:"validation-server" reload:"hot"` - RedisValidationClientConfig validatorclient.RedisValidationClientConfig `koanf:"redis-validation-client-config"` - ValidationServerConfigs []rpcclient.ClientConfig `koanf:"validation-server-configs" reload:"hot"` - ExecutionServerConfig rpcclient.ClientConfig `koanf:"execution-server-config" reload:"hot"` - ValidationPoll time.Duration `koanf:"validation-poll" reload:"hot"` - PrerecordedBlocks uint64 `koanf:"prerecorded-blocks" reload:"hot"` - ForwardBlocks uint64 `koanf:"forward-blocks" reload:"hot"` - CurrentModuleRoot string `koanf:"current-module-root"` // TODO(magic) requires reinitialization on hot reload - PendingUpgradeModuleRoot string `koanf:"pending-upgrade-module-root"` // TODO(magic) requires StatelessBlockValidator recreation on hot reload - FailureIsFatal bool `koanf:"failure-is-fatal" reload:"hot"` - Dangerous BlockValidatorDangerousConfig `koanf:"dangerous"` - MemoryFreeLimit string `koanf:"memory-free-limit" reload:"hot"` - ValidationServerConfigsList string `koanf:"validation-server-configs-list" reload:"hot"` + Enable bool `koanf:"enable"` + ValidationServer rpcclient.ClientConfig `koanf:"validation-server" reload:"hot"` + RedisValidationClientConfig redis.ValidationClientConfig `koanf:"redis-validation-client-config"` + ValidationServerConfigs []rpcclient.ClientConfig `koanf:"validation-server-configs" reload:"hot"` + ExecutionServerConfig rpcclient.ClientConfig `koanf:"execution-server-config" reload:"hot"` + ValidationPoll time.Duration `koanf:"validation-poll" reload:"hot"` + PrerecordedBlocks uint64 `koanf:"prerecorded-blocks" reload:"hot"` + ForwardBlocks uint64 `koanf:"forward-blocks" reload:"hot"` + CurrentModuleRoot string `koanf:"current-module-root"` // TODO(magic) requires reinitialization on hot reload + PendingUpgradeModuleRoot string `koanf:"pending-upgrade-module-root"` // TODO(magic) requires StatelessBlockValidator recreation on hot reload + FailureIsFatal bool `koanf:"failure-is-fatal" reload:"hot"` + Dangerous BlockValidatorDangerousConfig `koanf:"dangerous"` + MemoryFreeLimit string `koanf:"memory-free-limit" reload:"hot"` + ValidationServerConfigsList string `koanf:"validation-server-configs-list" reload:"hot"` memoryFreeLimit int } @@ -147,7 +146,7 @@ func BlockValidatorConfigAddOptions(prefix string, f *pflag.FlagSet) { f.Bool(prefix+".enable", DefaultBlockValidatorConfig.Enable, "enable block-by-block validation") rpcclient.RPCClientAddOptions(prefix+".validation-server", f, &DefaultBlockValidatorConfig.ValidationServer) rpcclient.RPCClientAddOptions(prefix+".execution-server-config", f, &DefaultBlockValidatorConfig.ExecutionServerConfig) - validatorclient.RedisValidationClientConfigAddOptions(prefix+".redis-validation-client-config", f) + redis.ValidationClientConfigAddOptions(prefix+".redis-validation-client-config", f) f.String(prefix+".validation-server-configs-list", DefaultBlockValidatorConfig.ValidationServerConfigsList, "array of validation rpc configs given as a json string. time duration should be supplied in number indicating nanoseconds") f.Duration(prefix+".validation-poll", DefaultBlockValidatorConfig.ValidationPoll, "poll time to check validations") f.Uint64(prefix+".forward-blocks", DefaultBlockValidatorConfig.ForwardBlocks, "prepare entries for up to that many blocks ahead of validation (small footprint)") @@ -168,7 +167,7 @@ var DefaultBlockValidatorConfig = BlockValidatorConfig{ ValidationServerConfigsList: "default", ValidationServer: rpcclient.DefaultClientConfig, ExecutionServerConfig: rpcclient.DefaultClientConfig, - RedisValidationClientConfig: validatorclient.DefaultRedisValidationClientConfig, + RedisValidationClientConfig: redis.DefaultValidationClientConfig, ValidationPoll: time.Second, ForwardBlocks: 1024, PrerecordedBlocks: uint64(2 * runtime.NumCPU()), @@ -183,7 +182,7 @@ var TestBlockValidatorConfig = BlockValidatorConfig{ Enable: false, ValidationServer: rpcclient.TestClientConfig, ValidationServerConfigs: []rpcclient.ClientConfig{rpcclient.TestClientConfig}, - RedisValidationClientConfig: validatorclient.TestRedisValidationClientConfig, + RedisValidationClientConfig: redis.TestValidationClientConfig, ExecutionServerConfig: rpcclient.TestClientConfig, ValidationPoll: 100 * time.Millisecond, ForwardBlocks: 128, @@ -1065,7 +1064,11 @@ func (v *BlockValidator) Initialize(ctx context.Context) error { } } log.Info("BlockValidator initialized", "current", v.currentWasmModuleRoot, "pending", v.pendingWasmModuleRoot) - if err := v.StatelessBlockValidator.Initialize([]common.Hash{v.currentWasmModuleRoot, v.pendingWasmModuleRoot}); err != nil { + moduleRoots := []common.Hash{v.currentWasmModuleRoot} + if v.pendingWasmModuleRoot != v.currentWasmModuleRoot { + moduleRoots = append(moduleRoots, v.pendingWasmModuleRoot) + } + if err := v.StatelessBlockValidator.Initialize(moduleRoots); err != nil { return fmt.Errorf("initializing block validator with module roots: %w", err) } return nil diff --git a/staker/stateless_block_validator.go b/staker/stateless_block_validator.go index 4f71e39545..f8e30329a7 100644 --- a/staker/stateless_block_validator.go +++ b/staker/stateless_block_validator.go @@ -22,6 +22,7 @@ import ( "github.com/offchainlabs/nitro/execution" "github.com/offchainlabs/nitro/util/rpcclient" "github.com/offchainlabs/nitro/validator" + "github.com/offchainlabs/nitro/validator/client/redis" validatorclient "github.com/offchainlabs/nitro/validator/client" ) @@ -196,7 +197,7 @@ func NewStatelessBlockValidator( ) (*StatelessBlockValidator, error) { var validationSpawners []validator.ValidationSpawner if config().RedisValidationClientConfig.Enabled() { - redisValClient, err := validatorclient.NewRedisValidationClient(&config().RedisValidationClientConfig) + redisValClient, err := redis.NewValidationClient(&config().RedisValidationClientConfig) if err != nil { return nil, fmt.Errorf("creating new redis validation client: %w", err) } @@ -229,7 +230,7 @@ func (v *StatelessBlockValidator) Initialize(moduleRoots []common.Hash) error { return nil } // First spawner is always RedisValidationClient if RedisStreams are enabled. - if v, ok := v.validationSpawners[0].(*validatorclient.RedisValidationClient); ok { + if v, ok := v.validationSpawners[0].(*redis.ValidationClient); ok { if err := v.Initialize(moduleRoots); err != nil { return fmt.Errorf("initializing redis validation client module roots: %w", err) } diff --git a/system_tests/block_validator_test.go b/system_tests/block_validator_test.go index a7c85bf5e4..c64fe22f54 100644 --- a/system_tests/block_validator_test.go +++ b/system_tests/block_validator_test.go @@ -27,8 +27,7 @@ import ( "github.com/offchainlabs/nitro/solgen/go/precompilesgen" "github.com/offchainlabs/nitro/util/arbmath" "github.com/offchainlabs/nitro/util/redisutil" - - validatorclient "github.com/offchainlabs/nitro/validator/client" + "github.com/offchainlabs/nitro/validator/client/redis" ) type workloadType uint @@ -73,7 +72,7 @@ func testBlockValidatorSimple(t *testing.T, dasModeString string, workloadLoops redisURL := "" if useRedisStreams { redisURL = redisutil.CreateTestRedis(ctx, t) - validatorConfig.BlockValidator.RedisValidationClientConfig = validatorclient.DefaultRedisValidationClientConfig + validatorConfig.BlockValidator.RedisValidationClientConfig = redis.DefaultValidationClientConfig validatorConfig.BlockValidator.RedisValidationClientConfig.RedisURL = redisURL validatorConfig.BlockValidator.ValidationServerConfigs = nil } diff --git a/system_tests/common_test.go b/system_tests/common_test.go index ebf903cfa8..5ad8aae08d 100644 --- a/system_tests/common_test.go +++ b/system_tests/common_test.go @@ -33,6 +33,7 @@ import ( "github.com/offchainlabs/nitro/validator/server_api" "github.com/offchainlabs/nitro/validator/server_common" "github.com/offchainlabs/nitro/validator/valnode" + rediscons "github.com/offchainlabs/nitro/validator/valnode/redis" "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/accounts/abi/bind" @@ -601,7 +602,7 @@ func AddDefaultValNode(t *testing.T, ctx context.Context, nodeConfig *arbnode.Co conf.UseJit = useJit // Enable redis streams when URL is specified if redisURL != "" { - conf.Arbitrator.RedisValidationServerConfig = server_api.DefaultRedisValidationServerConfig + conf.Arbitrator.RedisValidationServerConfig = rediscons.DefaultValidationServerConfig redisClient, err := redisutil.RedisClientFromURL(redisURL) if err != nil { t.Fatalf("Error creating redis coordinator: %v", err) diff --git a/validator/client/redisproducer.go b/validator/client/redis/producer.go similarity index 73% rename from validator/client/redisproducer.go rename to validator/client/redis/producer.go index 07569d51b6..da184e3c16 100644 --- a/validator/client/redisproducer.go +++ b/validator/client/redis/producer.go @@ -1,4 +1,4 @@ -package client +package redis import ( "context" @@ -18,39 +18,39 @@ import ( "github.com/spf13/pflag" ) -type RedisValidationClientConfig struct { +type ValidationClientConfig struct { Name string `koanf:"name"` Room int32 `koanf:"room"` RedisURL string `koanf:"redis-url"` ProducerConfig pubsub.ProducerConfig `koanf:"producer-config"` } -func (c RedisValidationClientConfig) Enabled() bool { +func (c ValidationClientConfig) Enabled() bool { return c.RedisURL != "" } -var DefaultRedisValidationClientConfig = RedisValidationClientConfig{ +var DefaultValidationClientConfig = ValidationClientConfig{ Name: "redis validation client", Room: 2, RedisURL: "", ProducerConfig: pubsub.DefaultProducerConfig, } -var TestRedisValidationClientConfig = RedisValidationClientConfig{ +var TestValidationClientConfig = ValidationClientConfig{ Name: "test redis validation client", Room: 2, RedisURL: "", ProducerConfig: pubsub.TestProducerConfig, } -func RedisValidationClientConfigAddOptions(prefix string, f *pflag.FlagSet) { - f.String(prefix+".name", DefaultRedisValidationClientConfig.Name, "validation client name") - f.Int32(prefix+".room", DefaultRedisValidationClientConfig.Room, "validation client room") +func ValidationClientConfigAddOptions(prefix string, f *pflag.FlagSet) { + f.String(prefix+".name", DefaultValidationClientConfig.Name, "validation client name") + f.Int32(prefix+".room", DefaultValidationClientConfig.Room, "validation client room") pubsub.ProducerAddConfigAddOptions(prefix+".producer-config", f) } -// RedisValidationClient implements validation client through redis streams. -type RedisValidationClient struct { +// ValidationClient implements validation client through redis streams. +type ValidationClient struct { stopwaiter.StopWaiter name string room int32 @@ -60,7 +60,7 @@ type RedisValidationClient struct { redisClient redis.UniversalClient } -func NewRedisValidationClient(cfg *RedisValidationClientConfig) (*RedisValidationClient, error) { +func NewValidationClient(cfg *ValidationClientConfig) (*ValidationClient, error) { if cfg.RedisURL == "" { return nil, fmt.Errorf("redis url cannot be empty") } @@ -68,7 +68,7 @@ func NewRedisValidationClient(cfg *RedisValidationClientConfig) (*RedisValidatio if err != nil { return nil, err } - return &RedisValidationClient{ + return &ValidationClient{ name: cfg.Name, room: cfg.Room, producers: make(map[common.Hash]*pubsub.Producer[*validator.ValidationInput, validator.GoGlobalState]), @@ -77,7 +77,7 @@ func NewRedisValidationClient(cfg *RedisValidationClientConfig) (*RedisValidatio }, nil } -func (c *RedisValidationClient) Initialize(moduleRoots []common.Hash) error { +func (c *ValidationClient) Initialize(moduleRoots []common.Hash) error { for _, mr := range moduleRoots { if _, exists := c.producers[mr]; exists { log.Warn("Producer already existsw for module root", "hash", mr) @@ -94,7 +94,7 @@ func (c *RedisValidationClient) Initialize(moduleRoots []common.Hash) error { return nil } -func (c *RedisValidationClient) Launch(entry *validator.ValidationInput, moduleRoot common.Hash) validator.ValidationRun { +func (c *ValidationClient) Launch(entry *validator.ValidationInput, moduleRoot common.Hash) validator.ValidationRun { atomic.AddInt32(&c.room, -1) defer atomic.AddInt32(&c.room, 1) producer, found := c.producers[moduleRoot] @@ -110,7 +110,7 @@ func (c *RedisValidationClient) Launch(entry *validator.ValidationInput, moduleR return server_common.NewValRun(promise, moduleRoot) } -func (c *RedisValidationClient) Start(ctx_in context.Context) error { +func (c *ValidationClient) Start(ctx_in context.Context) error { for _, p := range c.producers { p.Start(ctx_in) } @@ -118,20 +118,20 @@ func (c *RedisValidationClient) Start(ctx_in context.Context) error { return nil } -func (c *RedisValidationClient) Stop() { +func (c *ValidationClient) Stop() { for _, p := range c.producers { p.StopAndWait() } c.StopWaiter.StopAndWait() } -func (c *RedisValidationClient) Name() string { +func (c *ValidationClient) Name() string { if c.Started() { return c.name } return "(not started)" } -func (c *RedisValidationClient) Room() int { +func (c *ValidationClient) Room() int { return int(c.room) } diff --git a/validator/server_api/json.go b/validator/server_api/json.go index e1729b53aa..8c80768b14 100644 --- a/validator/server_api/json.go +++ b/validator/server_api/json.go @@ -8,10 +8,8 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/offchainlabs/nitro/arbutil" - "github.com/offchainlabs/nitro/pubsub" "github.com/offchainlabs/nitro/util/jsonapi" "github.com/offchainlabs/nitro/validator" - "github.com/spf13/pflag" ) const Namespace string = "validation" @@ -65,27 +63,3 @@ type BatchInfoJson struct { Number uint64 DataB64 string } - -type RedisValidationServerConfig struct { - RedisURL string `koanf:"redis-url"` - ConsumerConfig pubsub.ConsumerConfig `koanf:"consumer-config"` - // Supported wasm module roots. - ModuleRoots []string `koanf:"module-roots"` -} - -var DefaultRedisValidationServerConfig = RedisValidationServerConfig{ - RedisURL: "", - ConsumerConfig: pubsub.DefaultConsumerConfig, - ModuleRoots: []string{}, -} - -var TestRedisValidationServerConfig = RedisValidationServerConfig{ - RedisURL: "", - ConsumerConfig: pubsub.TestConsumerConfig, - ModuleRoots: []string{}, -} - -func RedisValidationServerConfigAddOptions(prefix string, f *pflag.FlagSet) { - pubsub.ConsumerConfigAddOptions(prefix+".consumer-config", f) - f.StringSlice(prefix+".module-roots", nil, "Supported module root hashes") -} diff --git a/validator/server_arb/validator_spawner.go b/validator/server_arb/validator_spawner.go index bc607d1088..e315b6a7fb 100644 --- a/validator/server_arb/validator_spawner.go +++ b/validator/server_arb/validator_spawner.go @@ -11,14 +11,14 @@ import ( "sync/atomic" "time" - flag "github.com/spf13/pflag" + "github.com/spf13/pflag" "github.com/offchainlabs/nitro/arbutil" "github.com/offchainlabs/nitro/util/containers" "github.com/offchainlabs/nitro/util/stopwaiter" "github.com/offchainlabs/nitro/validator" - "github.com/offchainlabs/nitro/validator/server_api" "github.com/offchainlabs/nitro/validator/server_common" + "github.com/offchainlabs/nitro/validator/valnode/redis" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/log" @@ -28,11 +28,11 @@ import ( var arbitratorValidationSteps = metrics.NewRegisteredHistogram("arbitrator/validation/steps", nil, metrics.NewBoundedHistogramSample()) type ArbitratorSpawnerConfig struct { - Workers int `koanf:"workers" reload:"hot"` - OutputPath string `koanf:"output-path" reload:"hot"` - Execution MachineCacheConfig `koanf:"execution" reload:"hot"` // hot reloading for new executions only - ExecutionRunTimeout time.Duration `koanf:"execution-run-timeout" reload:"hot"` - RedisValidationServerConfig server_api.RedisValidationServerConfig `koanf:"redis-validation-server-config"` + Workers int `koanf:"workers" reload:"hot"` + OutputPath string `koanf:"output-path" reload:"hot"` + Execution MachineCacheConfig `koanf:"execution" reload:"hot"` // hot reloading for new executions only + ExecutionRunTimeout time.Duration `koanf:"execution-run-timeout" reload:"hot"` + RedisValidationServerConfig redis.ValidationServerConfig `koanf:"redis-validation-server-config"` } type ArbitratorSpawnerConfigFecher func() *ArbitratorSpawnerConfig @@ -42,15 +42,15 @@ var DefaultArbitratorSpawnerConfig = ArbitratorSpawnerConfig{ OutputPath: "./target/output", Execution: DefaultMachineCacheConfig, ExecutionRunTimeout: time.Minute * 15, - RedisValidationServerConfig: server_api.DefaultRedisValidationServerConfig, + RedisValidationServerConfig: redis.DefaultValidationServerConfig, } -func ArbitratorSpawnerConfigAddOptions(prefix string, f *flag.FlagSet) { +func ArbitratorSpawnerConfigAddOptions(prefix string, f *pflag.FlagSet) { f.Int(prefix+".workers", DefaultArbitratorSpawnerConfig.Workers, "number of concurrent validation threads") f.Duration(prefix+".execution-run-timeout", DefaultArbitratorSpawnerConfig.ExecutionRunTimeout, "timeout before discarding execution run") f.String(prefix+".output-path", DefaultArbitratorSpawnerConfig.OutputPath, "path to write machines to") MachineCacheConfigConfigAddOptions(prefix+".execution", f) - server_api.RedisValidationServerConfigAddOptions(prefix+".redis-validation-server-config", f) + redis.ValidationServerConfigAddOptions(prefix+".redis-validation-server-config", f) } func DefaultArbitratorSpawnerConfigFetcher() *ArbitratorSpawnerConfig { diff --git a/validator/valnode/redisconsumer.go b/validator/valnode/redis/consumer.go similarity index 64% rename from validator/valnode/redisconsumer.go rename to validator/valnode/redis/consumer.go index d90868fb9e..1187474211 100644 --- a/validator/valnode/redisconsumer.go +++ b/validator/valnode/redis/consumer.go @@ -1,4 +1,4 @@ -package valnode +package redis import ( "context" @@ -12,11 +12,12 @@ import ( "github.com/offchainlabs/nitro/util/stopwaiter" "github.com/offchainlabs/nitro/validator" "github.com/offchainlabs/nitro/validator/server_api" + "github.com/spf13/pflag" ) -// RedisValidationServer implements consumer for the requests originated from +// ValidationServer implements consumer for the requests originated from // RedisValidationClient producers. -type RedisValidationServer struct { +type ValidationServer struct { stopwaiter.StopWaiter spawner validator.ValidationSpawner @@ -24,7 +25,7 @@ type RedisValidationServer struct { consumers map[common.Hash]*pubsub.Consumer[*validator.ValidationInput, validator.GoGlobalState] } -func NewRedisValidationServer(cfg *server_api.RedisValidationServerConfig, spawner validator.ValidationSpawner) (*RedisValidationServer, error) { +func NewValidationServer(cfg *ValidationServerConfig, spawner validator.ValidationSpawner) (*ValidationServer, error) { if cfg.RedisURL == "" { return nil, fmt.Errorf("redis url cannot be empty") } @@ -41,13 +42,13 @@ func NewRedisValidationServer(cfg *server_api.RedisValidationServerConfig, spawn } consumers[mr] = c } - return &RedisValidationServer{ + return &ValidationServer{ consumers: consumers, spawner: spawner, }, nil } -func (s *RedisValidationServer) Start(ctx_in context.Context) { +func (s *ValidationServer) Start(ctx_in context.Context) { s.StopWaiter.Start(ctx_in, s) for moduleRoot, c := range s.consumers { c := c @@ -76,3 +77,27 @@ func (s *RedisValidationServer) Start(ctx_in context.Context) { }) } } + +type ValidationServerConfig struct { + RedisURL string `koanf:"redis-url"` + ConsumerConfig pubsub.ConsumerConfig `koanf:"consumer-config"` + // Supported wasm module roots. + ModuleRoots []string `koanf:"module-roots"` +} + +var DefaultValidationServerConfig = ValidationServerConfig{ + RedisURL: "", + ConsumerConfig: pubsub.DefaultConsumerConfig, + ModuleRoots: []string{}, +} + +var TestValidationServerConfig = ValidationServerConfig{ + RedisURL: "", + ConsumerConfig: pubsub.TestConsumerConfig, + ModuleRoots: []string{}, +} + +func ValidationServerConfigAddOptions(prefix string, f *pflag.FlagSet) { + pubsub.ConsumerConfigAddOptions(prefix+".consumer-config", f) + f.StringSlice(prefix+".module-roots", nil, "Supported module root hashes") +} diff --git a/validator/valnode/valnode.go b/validator/valnode/valnode.go index bbb680087a..fab4531cba 100644 --- a/validator/valnode/valnode.go +++ b/validator/valnode/valnode.go @@ -8,12 +8,12 @@ import ( "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/node" "github.com/ethereum/go-ethereum/rpc" - flag "github.com/spf13/pflag" - "github.com/offchainlabs/nitro/validator/server_api" "github.com/offchainlabs/nitro/validator/server_arb" "github.com/offchainlabs/nitro/validator/server_common" "github.com/offchainlabs/nitro/validator/server_jit" + "github.com/offchainlabs/nitro/validator/valnode/redis" + "github.com/spf13/pflag" ) type WasmConfig struct { @@ -22,7 +22,7 @@ type WasmConfig struct { AllowedWasmModuleRoots []string `koanf:"allowed-wasm-module-roots"` } -func WasmConfigAddOptions(prefix string, f *flag.FlagSet) { +func WasmConfigAddOptions(prefix string, f *pflag.FlagSet) { f.String(prefix+".root-path", DefaultWasmConfig.RootPath, "path to machine folders, each containing wasm files (machine.wavm.br, replay.wasm)") f.Bool(prefix+".enable-wasmroots-check", DefaultWasmConfig.EnableWasmrootsCheck, "enable check for compatibility of on-chain WASM module root with node") f.StringSlice(prefix+".allowed-wasm-module-roots", DefaultWasmConfig.AllowedWasmModuleRoots, "list of WASM module roots to check if the on-chain WASM module root belongs to on node startup") @@ -63,7 +63,7 @@ var TestValidationConfig = Config{ Wasm: DefaultWasmConfig, } -func ValidationConfigAddOptions(prefix string, f *flag.FlagSet) { +func ValidationConfigAddOptions(prefix string, f *pflag.FlagSet) { f.Bool(prefix+".use-jit", DefaultValidationConfig.UseJit, "use jit for validation") f.Bool(prefix+".api-auth", DefaultValidationConfig.ApiAuth, "validate is an authenticated API") f.Bool(prefix+".api-public", DefaultValidationConfig.ApiPublic, "validate is a public API") @@ -77,7 +77,7 @@ type ValidationNode struct { arbSpawner *server_arb.ArbitratorSpawner jitSpawner *server_jit.JitSpawner - redisConsumer *RedisValidationServer + redisConsumer *redis.ValidationServer } func EnsureValidationExposedViaAuthRPC(stackConf *node.Config) { @@ -119,7 +119,7 @@ func CreateValidationNode(configFetcher ValidationConfigFetcher, stack *node.Nod } else { serverAPI = NewExecutionServerAPI(arbSpawner, arbSpawner, arbConfigFetcher) } - redisConsumer, err := NewRedisValidationServer(&arbConfigFetcher().RedisValidationServerConfig, arbSpawner) + redisConsumer, err := redis.NewValidationServer(&arbConfigFetcher().RedisValidationServerConfig, arbSpawner) if err != nil { log.Error("Creating new redis validation server", "error", err) } From db2eaf0340ff7e5f158254b294bd87c744f7afd3 Mon Sep 17 00:00:00 2001 From: Tsahi Zidenberg Date: Tue, 23 Apr 2024 16:59:00 -0600 Subject: [PATCH 17/17] valnode: only start redis validation if enabled --- validator/valnode/redis/consumer.go | 4 ++++ validator/valnode/valnode.go | 10 +++++++--- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/validator/valnode/redis/consumer.go b/validator/valnode/redis/consumer.go index 1187474211..1cadaf7c9a 100644 --- a/validator/valnode/redis/consumer.go +++ b/validator/valnode/redis/consumer.go @@ -101,3 +101,7 @@ func ValidationServerConfigAddOptions(prefix string, f *pflag.FlagSet) { pubsub.ConsumerConfigAddOptions(prefix+".consumer-config", f) f.StringSlice(prefix+".module-roots", nil, "Supported module root hashes") } + +func (cfg *ValidationServerConfig) Enabled() bool { + return cfg.RedisURL != "" +} diff --git a/validator/valnode/valnode.go b/validator/valnode/valnode.go index fab4531cba..93a5b37238 100644 --- a/validator/valnode/valnode.go +++ b/validator/valnode/valnode.go @@ -119,9 +119,13 @@ func CreateValidationNode(configFetcher ValidationConfigFetcher, stack *node.Nod } else { serverAPI = NewExecutionServerAPI(arbSpawner, arbSpawner, arbConfigFetcher) } - redisConsumer, err := redis.NewValidationServer(&arbConfigFetcher().RedisValidationServerConfig, arbSpawner) - if err != nil { - log.Error("Creating new redis validation server", "error", err) + var redisConsumer *redis.ValidationServer + redisValidationConfig := arbConfigFetcher().RedisValidationServerConfig + if redisValidationConfig.Enabled() { + redisConsumer, err = redis.NewValidationServer(&redisValidationConfig, arbSpawner) + if err != nil { + log.Error("Creating new redis validation server", "error", err) + } } valAPIs := []rpc.API{{ Namespace: server_api.Namespace,