Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

op-program: Introduce basic infrastructure for fault proof program #5297

Merged
merged 4 commits into from
Mar 31, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -1123,6 +1123,9 @@ workflows:
- go-lint:
name: op-proposer-lint
module: op-proposer
- go-lint:
name: op-program-lint
module: op-program
- go-lint:
name: op-service-lint
module: op-service
Expand All @@ -1145,6 +1148,9 @@ workflows:
- go-test:
name: op-proposer-tests
module: op-proposer
- go-test:
name: op-program-tests
module: op-program
- go-test:
name: op-service-tests
module: op-service
Expand All @@ -1164,12 +1170,14 @@ workflows:
- op-e2e-lint
- op-node-lint
- op-proposer-lint
- op-program-lint
- op-service-lint
- op-batcher-tests
- op-bindings-tests
- op-chain-ops-tests
- op-node-tests
- op-proposer-tests
- op-program-tests
- op-service-tests
- op-e2e-WS-tests
- op-e2e-HTTP-tests
Expand Down
1 change: 1 addition & 0 deletions .github/CODEOWNERS
Validating CODEOWNERS rules …
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
/op-node @ethereum-optimism/go-reviewers
/op-node/rollup @protolambda @trianglesphere
/op-proposer @ethereum-optimism/go-reviewers
/op-program @ethereum-optimism/go-reviewers
ajsutton marked this conversation as resolved.
Show resolved Hide resolved
/op-service @ethereum-optimism/go-reviewers

# Ops
Expand Down
4 changes: 4 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,10 @@ op-proposer:
make -C ./op-proposer op-proposer
.PHONY: op-proposer

op-program:
make -C ./op-program op-program
.PHONY: op-program

mod-tidy:
# Below GOPRIVATE line allows mod-tidy to be run immediately after
# releasing new versions. This bypasses the Go modules proxy, which
Expand Down
1 change: 1 addition & 0 deletions op-program/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
bin
21 changes: 21 additions & 0 deletions op-program/LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
MIT License

Copyright (c) 2023 Optimism

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
27 changes: 27 additions & 0 deletions op-program/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
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 github.com/ethereum-optimism/optimism/op-program/version.Version=$(VERSION)
LDFLAGSSTRING +=-X github.com/ethereum-optimism/optimism/op-program/version.Meta=$(VERSION_META)
LDFLAGS := -ldflags "$(LDFLAGSSTRING)"

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

clean:
rm -rf bin

test:
go test -v ./...

lint:
golangci-lint run -E goimports,sqlclosecheck,bodyclose,asciicheck,misspell,errorlint -e "errors.As" -e "errors.Is"

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

Implements a fault proof program that runs through the rollup state-transition to verify an L2 output from L1 inputs.
This verifiable output can then resolve a disputed output on L1.

The program is designed such that it can be run in a deterministic way such that two invocations with the same input
data wil result in not only the same output, but the same program execution trace. This allows it to be run in an
on-chain VM as part of the dispute resolution process.

## Compiling

To build op-program, from within the `op-program` directory run:

```shell
make op-program
```

This resulting executable will be in `./bin/op-program`

## Testing

To run op-program unit tests, from within the `op-program` directory run:

```shell
make test
```

## Lint

To run the linter, from within the `op-program` directory run:
```shell
make lint
```

This requires having `golangci-lint` installed.

## Running

From within the `op-program` directory, options can be reviewed with:

```shell
./bin/op-program --help
```
90 changes: 90 additions & 0 deletions op-program/cmd/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
package main

import (
"fmt"
"os"

"github.com/ethereum-optimism/optimism/op-node/chaincfg"
"github.com/ethereum-optimism/optimism/op-program/config"
"github.com/ethereum-optimism/optimism/op-program/flags"
"github.com/ethereum-optimism/optimism/op-program/version"
oplog "github.com/ethereum-optimism/optimism/op-service/log"
"github.com/ethereum/go-ethereum/log"
"github.com/urfave/cli"
)

var (
GitCommit = ""
GitDate = ""
)

// VersionWithMeta holds the textual version string including the metadata.
var VersionWithMeta = func() string {
v := version.Version
if GitCommit != "" {
v += "-" + GitCommit[:8]
}
if GitDate != "" {
v += "-" + GitDate
}
if version.Meta != "" {
v += "-" + version.Meta
}
return v
}()

func main() {
args := os.Args
err := run(args, FaultProofProgram)
if err != nil {
log.Crit("Application failed", "message", err)
}
}

type ConfigAction func(log log.Logger, config *config.Config) error

// run parses the supplied args to create a config.Config instance, sets up logging
// then calls the supplied ConfigAction.
// This allows testing the translation from CLI arguments to Config
func run(args []string, action ConfigAction) error {
// Set up logger with a default INFO level in case we fail to parse flags,
// otherwise the final critical log won't show what the parsing error was.
oplog.SetupDefaults()

app := cli.NewApp()
app.Version = VersionWithMeta
app.Flags = flags.Flags
app.Name = "op-program"
app.Usage = "Optimism Fault Proof Program"
app.Description = "The Optimism Fault Proof Program fault proof program that runs through the rollup state-transition to verify an L2 output from L1 inputs."
app.Action = func(ctx *cli.Context) error {
logger, err := setupLogging(ctx)
if err != nil {
return err
}
logger.Info("Starting fault proof program", "version", VersionWithMeta)

cfg, err := config.NewConfigFromCLI(ctx)
if err != nil {
return err
}
return action(logger, cfg)
}

return app.Run(args)
}

func setupLogging(ctx *cli.Context) (log.Logger, error) {
logCfg := oplog.ReadCLIConfig(ctx)
if err := logCfg.Check(); err != nil {
return nil, fmt.Errorf("log config error: %w", err)
}
logger := oplog.NewLogger(logCfg)
return logger, nil
}

// FaultProofProgram is the programmatic entry-point for the fault proof program
func FaultProofProgram(log log.Logger, cfg *config.Config) error {
trianglesphere marked this conversation as resolved.
Show resolved Hide resolved
cfg.Rollup.LogDescription(log, chaincfg.L2ChainIDToNetworkName)
return nil
}
125 changes: 125 additions & 0 deletions op-program/cmd/main_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
package main

import (
"encoding/json"
"os"
"testing"

"github.com/ethereum-optimism/optimism/op-node/chaincfg"
"github.com/ethereum-optimism/optimism/op-program/config"
"github.com/ethereum/go-ethereum/log"
"github.com/stretchr/testify/require"
)

func TestLogLevel(t *testing.T) {
t.Run("RejectInvalid", func(t *testing.T) {
verifyArgsInvalid(t, "unknown level: foo", addRequiredArgs("--log.level=foo"))
})

for _, lvl := range []string{"trace", "debug", "info", "error", "crit"} {
lvl := lvl
t.Run("AcceptValid_"+lvl, func(t *testing.T) {
logger, _, err := runWithArgs(addRequiredArgs("--log.level", lvl))
require.NoError(t, err)
require.NotNil(t, logger)
})
}
}

func TestDefaultCLIOptionsMatchDefaultConfig(t *testing.T) {
cfg := configForArgs(t, addRequiredArgs())
require.Equal(t, config.NewConfig(&chaincfg.Goerli), cfg)
}

func TestNetwork(t *testing.T) {
t.Run("Unknown", func(t *testing.T) {
verifyArgsInvalid(t, "invalid network bar", replaceRequiredArg("--network", "bar"))
})

t.Run("Required", func(t *testing.T) {
verifyArgsInvalid(t, "flag rollup.config or network is required", addRequiredArgsExcept("--network"))
})

t.Run("DisallowNetworkAndRollupConfig", func(t *testing.T) {
verifyArgsInvalid(t, "cannot specify both rollup.config and network", addRequiredArgs("--rollup.config=foo"))
})

t.Run("RollupConfig", func(t *testing.T) {
dir := t.TempDir()
configJson, err := json.Marshal(chaincfg.Goerli)
require.NoError(t, err)
configFile := dir + "/config.json"
err = os.WriteFile(configFile, configJson, os.ModePerm)
require.NoError(t, err)

cfg := configForArgs(t, addRequiredArgsExcept("--network", "--rollup.config", configFile))
require.Equal(t, chaincfg.Goerli, *cfg.Rollup)
})

for name, cfg := range chaincfg.NetworksByName {
name := name
expected := cfg
t.Run("Network_"+name, func(t *testing.T) {
cfg := configForArgs(t, replaceRequiredArg("--network", name))
require.Equal(t, expected, *cfg.Rollup)
})
}
}

func verifyArgsInvalid(t *testing.T, messageContains string, cliArgs []string) {
_, _, err := runWithArgs(cliArgs)
require.ErrorContains(t, err, messageContains)
}

func configForArgs(t *testing.T, cliArgs []string) *config.Config {
_, cfg, err := runWithArgs(cliArgs)
require.NoError(t, err)
return cfg
}

func runWithArgs(cliArgs []string) (log.Logger, *config.Config, error) {
var cfg *config.Config
var logger log.Logger
fullArgs := append([]string{"op-program"}, cliArgs...)
err := run(fullArgs, func(log log.Logger, config *config.Config) error {
logger = log
cfg = config
return nil
})
return logger, cfg, err
}

func addRequiredArgs(args ...string) []string {
req := requiredArgs()
combined := toArgList(req)
return append(combined, args...)
}

func addRequiredArgsExcept(name string, optionalArgs ...string) []string {
req := requiredArgs()
delete(req, name)
return append(toArgList(req), optionalArgs...)
}

func replaceRequiredArg(name string, value string) []string {
req := requiredArgs()
req[name] = value
return toArgList(req)
}

// requiredArgs returns map of argument names to values which are the minimal arguments required
// to create a valid Config
func requiredArgs() map[string]string {
return map[string]string{
"--network": "goerli",
}
}

func toArgList(req map[string]string) []string {
var combined []string
for name, value := range req {
combined = append(combined, name)
combined = append(combined, value)
}
return combined
}
Loading