Skip to content

Commit

Permalink
[op-conductor] Initial setup (ethereum-optimism#8642)
Browse files Browse the repository at this point in the history
* Initial setup for op-conductor

* Rework based on feedback

* Update README

* Update README

* Rework flags structure

* Add tests

* Add CI configs

* Add dockerfile

* Remove TODO

---------

Co-authored-by: Matthew Slipper <[email protected]>
  • Loading branch information
0x00101010 and mslipper authored Dec 19, 2023
1 parent 7e57109 commit f3da73d
Show file tree
Hide file tree
Showing 19 changed files with 590 additions and 90 deletions.
36 changes: 36 additions & 0 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -1493,6 +1493,10 @@ workflows:
name: op-challenger-tests
module: op-challenger
requires: ["op-stack-go-lint"]
- go-test:
name: op-conductor-tests
module: op-conductor
requires: ["op-stack-go-lint"]
- go-test:
name: op-program-tests
module: op-program
Expand Down Expand Up @@ -1547,6 +1551,7 @@ workflows:
- op-node-tests
- op-proposer-tests
- op-challenger-tests
- op-conductor-tests
- op-program-tests
- op-program-compat
- op-service-tests
Expand Down Expand Up @@ -1589,6 +1594,12 @@ workflows:
docker_tags: <<pipeline.git.revision>>,<<pipeline.git.branch>>
requires: ['op-stack-go-docker-build']
save_image_tag: <<pipeline.git.revision>> # for devnet later
- docker-build:
name: op-conductor-docker-build
docker_name: op-conductor
docker_tags: <<pipeline.git.revision>>,<<pipeline.git.branch>>
requires: ['op-stack-go-docker-build']
save_image_tag: <<pipeline.git.revision>> # for devnet later
- docker-build:
name: op-heartbeat-docker-build
docker_name: op-heartbeat
Expand Down Expand Up @@ -1717,6 +1728,21 @@ workflows:
release: true
context:
- oplabs-gcr-release
- docker-build:
name: op-conductor-docker-release
filters:
tags:
only: /^op-conductor\/v.*/
branches:
ignore: /.*/
docker_name: op-conductor
docker_tags: <<pipeline.git.revision>>
requires: ['op-stack-go-docker-build-release']
platforms: "linux/amd64,linux/arm64"
publish: true
release: true
context:
- oplabs-gcr-release
- docker-build:
name: op-ufm-docker-release
filters:
Expand Down Expand Up @@ -1897,6 +1923,16 @@ workflows:
context:
- oplabs-gcr
- slack
- docker-build:
name: op-conductor-docker-publish
docker_name: op-conductor
docker_tags: <<pipeline.git.revision>>,<<pipeline.git.branch>>
requires: [ 'op-stack-go-docker-build-publish' ]
platforms: "linux/amd64,linux/arm64"
publish: true
context:
- oplabs-gcr
- slack
- docker-build:
name: op-heartbeat-docker-publish
docker_name: op-heartbeat
Expand Down
13 changes: 13 additions & 0 deletions docker-bake.hcl
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,19 @@ target "op-challenger" {
tags = [for tag in split(",", IMAGE_TAGS) : "${REGISTRY}/${REPOSITORY}/op-challenger:${tag}"]
}

target "op-conductor" {
dockerfile = "Dockerfile"
context = "./op-conductor"
args = {
OP_STACK_GO_BUILDER = "op-stack-go"
}
contexts = {
op-stack-go: "target:op-stack-go"
}
platforms = split(",", PLATFORMS)
tags = [for tag in split(",", IMAGE_TAGS) : "${REGISTRY}/${REPOSITORY}/op-conductor:${tag}"]
}

target "op-heartbeat" {
dockerfile = "Dockerfile"
context = "./op-heartbeat"
Expand Down
28 changes: 4 additions & 24 deletions op-bootnode/flags/flags.go
Original file line number Diff line number Diff line change
@@ -1,39 +1,19 @@
package flags

import (
"fmt"
"strings"
"github.com/urfave/cli/v2"

"github.com/ethereum-optimism/optimism/op-node/chaincfg"
"github.com/ethereum-optimism/optimism/op-node/flags"
opservice "github.com/ethereum-optimism/optimism/op-service"
opflags "github.com/ethereum-optimism/optimism/op-service/flags"
oplog "github.com/ethereum-optimism/optimism/op-service/log"
opmetrics "github.com/ethereum-optimism/optimism/op-service/metrics"
"github.com/urfave/cli/v2"
)

const envVarPrefix = "OP_BOOTNODE"

func prefixEnvVars(name string) []string {
return opservice.PrefixEnvVar(envVarPrefix, name)
}

var (
RollupConfig = &cli.StringFlag{
Name: flags.RollupConfig.Name,
Usage: "Rollup chain parameters",
EnvVars: prefixEnvVars("ROLLUP_CONFIG"),
}
Network = &cli.StringFlag{
Name: flags.Network.Name,
Usage: fmt.Sprintf("Predefined network selection. Available networks: %s", strings.Join(chaincfg.AvailableNetworks(), ", ")),
EnvVars: prefixEnvVars("NETWORK"),
}
)

var Flags = []cli.Flag{
RollupConfig,
Network,
opflags.CLINetworkFlag(envVarPrefix),
opflags.CLIRollupConfigFlag(envVarPrefix),
}

func init() {
Expand Down
1 change: 1 addition & 0 deletions op-conductor/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
bin
9 changes: 9 additions & 0 deletions op-conductor/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
ARG OP_STACK_GO_BUILDER=us-docker.pkg.dev/oplabs-tools-artifacts/images/op-stack-go:latest
FROM $OP_STACK_GO_BUILDER as builder
# See "make golang-docker" and /ops/docker/op-stack-go

FROM alpine:3.18

COPY --from=builder /usr/local/bin/op-conductor /usr/local/bin/op-conductor

CMD ["op-conductor"]
22 changes: 22 additions & 0 deletions op-conductor/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
GITCOMMIT ?= $(shell git rev-parse HEAD)
GITDATE ?= $(shell git show -s --format='%ct')
VERSION := v0.0.0

LDFLAGSSTRING +=-X main.GitCommit=$(GITCOMMIT)
LDFLAGSSTRING +=-X main.GitDate=$(GITDATE)
LDFLAGSSTRING +=-X main.Version=$(VERSION)
LDFLAGS := -ldflags "$(LDFLAGSSTRING)"

op-conductor:
env GO111MODULE=on GOOS=$(TARGETOS) GOARCH=$(TARGETARCH) go build -v $(LDFLAGS) -o ./bin/op-conductor ./cmd

clean:
rm bin/op-conductor

test:
go test -v ./...

.PHONY: \
clean \
op-conductor \
test
30 changes: 30 additions & 0 deletions op-conductor/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# op-conductor

op-conductor is an auxiliary service designed to enhance the reliability and availability of a sequencer in
high-availability setups, thereby minimizing the risks associated with single point of failure.
It is important to note, however, that this design does not incorporate Byzantine fault tolerance.
This means it operates under the assumption that all participating nodes are honest.

## Summary

The design will provide below guarantees:

1. No unsafe reorgs
2. No unsafe head stall during network partition
3. 100% uptime with no more than 1 node failure (for a standard 3 node setup)

## Design

![op-conductor architecture](./assets/op-conductor.svg)
On a high level, op-conductor serves the following functions:

1. serves as a (raft) consensus layer participant to determine
1. leader of the sequencers
2. store latest unsafe block within its state machine.
2. serves rpc requests for
1. admin rpc for manual recovery scenarios such as stop leadership vote, remove itself from cluster, etc
2. health rpc for op-node to determine if it should allow publish txs / unsafe blocks
3. monitor sequencer (op-node) health
4. control loop => control sequencer (op-node) status (start / stop) based on different scenarios

This is initial version of README, more details will be added later.
1 change: 1 addition & 0 deletions op-conductor/assets/op-conductor.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
61 changes: 61 additions & 0 deletions op-conductor/cmd/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package main

import (
"context"
"fmt"
"os"

"github.com/ethereum/go-ethereum/log"
"github.com/urfave/cli/v2"

"github.com/ethereum-optimism/optimism/op-conductor/conductor"
"github.com/ethereum-optimism/optimism/op-conductor/flags"
opservice "github.com/ethereum-optimism/optimism/op-service"
"github.com/ethereum-optimism/optimism/op-service/cliapp"
oplog "github.com/ethereum-optimism/optimism/op-service/log"
"github.com/ethereum-optimism/optimism/op-service/opio"
)

var (
Version = "v0.0.1"
GitCommit = ""
GitDate = ""
)

func main() {
oplog.SetupDefaults()

app := cli.NewApp()
app.Flags = cliapp.ProtectFlags(flags.Flags)
app.Version = opservice.FormatVersion(Version, GitCommit, GitDate, "")
app.Name = "op-conductor"
app.Usage = "Optimism Sequencer Conductor Service"
app.Description = "op-conductor help sequencer to run in highly available mode"
app.Action = cliapp.LifecycleCmd(OpConductorMain)
app.Commands = []*cli.Command{}

ctx := opio.WithInterruptBlocker(context.Background())
err := app.RunContext(ctx, os.Args)
if err != nil {
log.Crit("Application failed", "message", err)
}
}

func OpConductorMain(ctx *cli.Context, closeApp context.CancelCauseFunc) (cliapp.Lifecycle, error) {
logCfg := oplog.ReadCLIConfig(ctx)
log := oplog.NewLogger(oplog.AppOut(ctx), logCfg)
oplog.SetGlobalLogHandler(log.GetHandler())
opservice.ValidateEnvVars(flags.EnvVarPrefix, flags.Flags, log)

cfg, err := conductor.NewConfig(ctx, log)
if err != nil {
return nil, fmt.Errorf("failed to read config: %w", err)
}

c, err := conductor.New(ctx.Context, cfg, log, Version)
if err != nil {
return nil, fmt.Errorf("failed to create conductor: %w", err)
}

return c, nil
}
101 changes: 101 additions & 0 deletions op-conductor/conductor/config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
package conductor

import (
"fmt"

"github.com/ethereum/go-ethereum/log"
"github.com/pkg/errors"
"github.com/urfave/cli/v2"

"github.com/ethereum-optimism/optimism/op-conductor/flags"
opnode "github.com/ethereum-optimism/optimism/op-node"
"github.com/ethereum-optimism/optimism/op-node/rollup"
oplog "github.com/ethereum-optimism/optimism/op-service/log"
opmetrics "github.com/ethereum-optimism/optimism/op-service/metrics"
oppprof "github.com/ethereum-optimism/optimism/op-service/pprof"
oprpc "github.com/ethereum-optimism/optimism/op-service/rpc"
)

type Config struct {
// ConsensusAddr is the address to listen for consensus connections.
ConsensusAddr string

// ConsensusPort is the port to listen for consensus connections.
ConsensusPort int

// RaftServerID is the unique ID for this server used by raft consensus.
RaftServerID string

// RaftStorageDir is the directory to store raft data.
RaftStorageDir string

// RaftBootstrap is true if this node should bootstrap a new raft cluster.
RaftBootstrap bool

// NodeRPC is the HTTP provider URL for op-node.
NodeRPC string

RollupCfg rollup.Config

LogConfig oplog.CLIConfig
MetricsConfig opmetrics.CLIConfig
PprofConfig oppprof.CLIConfig
RPC oprpc.CLIConfig
}

// Check validates the CLIConfig.
func (c *Config) Check() error {
if c.ConsensusAddr == "" {
return fmt.Errorf("missing consensus address")
}
if c.ConsensusPort == 0 {
return fmt.Errorf("missing consensus port")
}
if c.RaftServerID == "" {
return fmt.Errorf("missing raft server ID")
}
if c.RaftStorageDir == "" {
return fmt.Errorf("missing raft storage directory")
}
if c.NodeRPC == "" {
return fmt.Errorf("missing node RPC")
}
if err := c.RollupCfg.Check(); err != nil {
return errors.Wrap(err, "invalid rollup config")
}
if err := c.MetricsConfig.Check(); err != nil {
return errors.Wrap(err, "invalid metrics config")
}
if err := c.PprofConfig.Check(); err != nil {
return errors.Wrap(err, "invalid pprof config")
}
if err := c.RPC.Check(); err != nil {
return errors.Wrap(err, "invalid rpc config")
}
return nil
}

// NewConfig parses the Config from the provided flags or environment variables.
func NewConfig(ctx *cli.Context, log log.Logger) (*Config, error) {
if err := flags.CheckRequired(ctx); err != nil {
return nil, errors.Wrap(err, "missing required flags")
}

rollupCfg, err := opnode.NewRollupConfig(log, ctx)
if err != nil {
return nil, errors.Wrap(err, "failed to load rollup config")
}

return &Config{
ConsensusAddr: ctx.String(flags.ConsensusAddr.Name),
ConsensusPort: ctx.Int(flags.ConsensusPort.Name),
RaftServerID: ctx.String(flags.RaftServerID.Name),
RaftStorageDir: ctx.String(flags.RaftStorageDir.Name),
NodeRPC: ctx.String(flags.NodeRPC.Name),
RollupCfg: *rollupCfg,
LogConfig: oplog.ReadCLIConfig(ctx),
MetricsConfig: opmetrics.ReadCLIConfig(ctx),
PprofConfig: oppprof.ReadCLIConfig(ctx),
RPC: oprpc.ReadCLIConfig(ctx),
}, nil
}
Loading

0 comments on commit f3da73d

Please sign in to comment.