Commit a382b9e2 authored by mergify[bot]'s avatar mergify[bot] Committed by GitHub

Merge branch 'develop' into indexer.sql.constraints

parents a9ef72e1 d7ac3e97
...@@ -119,7 +119,7 @@ Note that these environment variables significantly speed up build time. ...@@ -119,7 +119,7 @@ Note that these environment variables significantly speed up build time.
cd ops-bedrock cd ops-bedrock
export COMPOSE_DOCKER_CLI_BUILD=1 export COMPOSE_DOCKER_CLI_BUILD=1
export DOCKER_BUILDKIT=1 export DOCKER_BUILDKIT=1
docker-compose build docker compose build
``` ```
Source code changes can have an impact on more than one container. Source code changes can have an impact on more than one container.
...@@ -127,9 +127,9 @@ Source code changes can have an impact on more than one container. ...@@ -127,9 +127,9 @@ Source code changes can have an impact on more than one container.
```bash ```bash
cd ops-bedrock cd ops-bedrock
docker-compose down docker compose down
docker-compose build docker compose build
docker-compose up docker compose up
``` ```
**If a node process exits with exit code: 137** you may need to increase the default memory limit of docker containers **If a node process exits with exit code: 137** you may need to increase the default memory limit of docker containers
...@@ -141,18 +141,18 @@ cd optimism ...@@ -141,18 +141,18 @@ cd optimism
pnpm clean pnpm clean
pnpm build pnpm build
cd ops cd ops
docker-compose down -v docker compose down -v
docker-compose build docker compose build
docker-compose up docker compose up
``` ```
#### Viewing docker container logs #### Viewing docker container logs
By default, the `docker-compose up` command will show logs from all services, and that By default, the `docker compose up` command will show logs from all services, and that
can be hard to filter through. In order to view the logs from a specific service, you can run: can be hard to filter through. In order to view the logs from a specific service, you can run:
```bash ```bash
docker-compose logs --follow <service name> docker compose logs --follow <service name>
``` ```
### Running tests ### Running tests
......
...@@ -101,13 +101,13 @@ devnet-test: ...@@ -101,13 +101,13 @@ devnet-test:
.PHONY: devnet-test .PHONY: devnet-test
devnet-down: devnet-down:
@(cd ./ops-bedrock && GENESIS_TIMESTAMP=$(shell date +%s) docker-compose stop) @(cd ./ops-bedrock && GENESIS_TIMESTAMP=$(shell date +%s) docker compose stop)
.PHONY: devnet-down .PHONY: devnet-down
devnet-clean: devnet-clean:
rm -rf ./packages/contracts-bedrock/deployments/devnetL1 rm -rf ./packages/contracts-bedrock/deployments/devnetL1
rm -rf ./.devnet rm -rf ./.devnet
cd ./ops-bedrock && docker-compose down cd ./ops-bedrock && docker compose down
docker image ls 'ops-bedrock*' --format='{{.Repository}}' | xargs -r docker rmi docker image ls 'ops-bedrock*' --format='{{.Repository}}' | xargs -r docker rmi
docker volume ls --filter name=ops-bedrock --format='{{.Name}}' | xargs -r docker volume rm docker volume ls --filter name=ops-bedrock --format='{{.Name}}' | xargs -r docker volume rm
.PHONY: devnet-clean .PHONY: devnet-clean
...@@ -116,7 +116,7 @@ devnet-allocs: ...@@ -116,7 +116,7 @@ devnet-allocs:
PYTHONPATH=./bedrock-devnet python3 ./bedrock-devnet/main.py --monorepo-dir=. --allocs PYTHONPATH=./bedrock-devnet python3 ./bedrock-devnet/main.py --monorepo-dir=. --allocs
devnet-logs: devnet-logs:
@(cd ./ops-bedrock && docker-compose logs -f) @(cd ./ops-bedrock && docker compose logs -f)
.PHONY: devnet-logs .PHONY: devnet-logs
test-unit: test-unit:
......
...@@ -93,7 +93,7 @@ def main(): ...@@ -93,7 +93,7 @@ def main():
return return
log.info('Building docker images') log.info('Building docker images')
run_command(['docker-compose', 'build', '--progress', 'plain'], cwd=paths.ops_bedrock_dir, env={ run_command(['docker', 'compose', 'build', '--progress', 'plain'], cwd=paths.ops_bedrock_dir, env={
'PWD': paths.ops_bedrock_dir 'PWD': paths.ops_bedrock_dir
}) })
...@@ -173,7 +173,7 @@ def devnet_deploy(paths): ...@@ -173,7 +173,7 @@ def devnet_deploy(paths):
], cwd=paths.op_node_dir) ], cwd=paths.op_node_dir)
log.info('Starting L1.') log.info('Starting L1.')
run_command(['docker-compose', 'up', '-d', 'l1'], cwd=paths.ops_bedrock_dir, env={ run_command(['docker', 'compose', 'up', '-d', 'l1'], cwd=paths.ops_bedrock_dir, env={
'PWD': paths.ops_bedrock_dir 'PWD': paths.ops_bedrock_dir
}) })
wait_up(8545) wait_up(8545)
...@@ -196,7 +196,7 @@ def devnet_deploy(paths): ...@@ -196,7 +196,7 @@ def devnet_deploy(paths):
addresses = read_json(paths.addresses_json_path) addresses = read_json(paths.addresses_json_path)
log.info('Bringing up L2.') log.info('Bringing up L2.')
run_command(['docker-compose', 'up', '-d', 'l2'], cwd=paths.ops_bedrock_dir, env={ run_command(['docker', 'compose', 'up', '-d', 'l2'], cwd=paths.ops_bedrock_dir, env={
'PWD': paths.ops_bedrock_dir 'PWD': paths.ops_bedrock_dir
}) })
wait_up(9545) wait_up(9545)
...@@ -208,7 +208,7 @@ def devnet_deploy(paths): ...@@ -208,7 +208,7 @@ def devnet_deploy(paths):
log.info(f'Using batch inbox {batch_inbox_address}') log.info(f'Using batch inbox {batch_inbox_address}')
log.info('Bringing up everything else.') log.info('Bringing up everything else.')
run_command(['docker-compose', 'up', '-d', 'op-node', 'op-proposer', 'op-batcher'], cwd=paths.ops_bedrock_dir, env={ run_command(['docker', 'compose', 'up', '-d', 'op-node', 'op-proposer', 'op-batcher'], cwd=paths.ops_bedrock_dir, env={
'PWD': paths.ops_bedrock_dir, 'PWD': paths.ops_bedrock_dir,
'L2OO_ADDRESS': l2_output_oracle, 'L2OO_ADDRESS': l2_output_oracle,
'SEQUENCER_BATCH_INBOX_ADDRESS': batch_inbox_address 'SEQUENCER_BATCH_INBOX_ADDRESS': batch_inbox_address
......
FROM golang:1.19.9-alpine3.16 as builder FROM golang:1.20.7-alpine3.18 as builder
RUN apk --no-cache add make jq bash git alpine-sdk RUN apk --no-cache add make jq bash git alpine-sdk
...@@ -16,7 +16,7 @@ RUN go mod download ...@@ -16,7 +16,7 @@ RUN go mod download
RUN make build RUN make build
FROM alpine:3.16 FROM alpine:3.18
RUN apk --no-cache add ca-certificates RUN apk --no-cache add ca-certificates
RUN addgroup -S app && adduser -S app -G app RUN addgroup -S app && adduser -S app -G app
......
module github.com/ethereum-optimism/optimism module github.com/ethereum-optimism/optimism
go 1.19 go 1.20
require ( require (
github.com/BurntSushi/toml v1.3.2 github.com/BurntSushi/toml v1.3.2
......
FROM --platform=$BUILDPLATFORM golang:1.19.9-alpine3.16 as builder FROM --platform=$BUILDPLATFORM golang:1.20.7-alpine3.18 as builder
RUN apk add --no-cache make gcc musl-dev linux-headers git jq bash RUN apk add --no-cache make gcc musl-dev linux-headers git jq bash
...@@ -18,7 +18,7 @@ RUN go mod download ...@@ -18,7 +18,7 @@ RUN go mod download
RUN make indexer RUN make indexer
FROM alpine:3.16 FROM alpine:3.18
COPY --from=builder /app/indexer/indexer /usr/local/bin COPY --from=builder /app/indexer/indexer /usr/local/bin
......
...@@ -16,7 +16,7 @@ All tests can be ran by running `make test` from the `/indexer` directory. This ...@@ -16,7 +16,7 @@ All tests can be ran by running `make test` from the `/indexer` directory. This
- install docker - install docker
- `cp example.env .env` - `cp example.env .env`
- fill in .env - fill in .env
- run `docker-compose up` to start the indexer vs optimism goerli network - run `docker compose up` to start the indexer vs optimism goerli network
### Run indexer with go ### Run indexer with go
......
...@@ -7,7 +7,7 @@ A simple UI for exploring the indexer DB using [Prisma studio](https://www.prism ...@@ -7,7 +7,7 @@ A simple UI for exploring the indexer DB using [Prisma studio](https://www.prism
Included in the docker-compose file as `ui` service Included in the docker-compose file as `ui` service
```bash ```bash
docker-compose up docker compose up
``` ```
Prisma can be viewed at [localhost:5555](http://localhost:5555) Prisma can be viewed at [localhost:5555](http://localhost:5555)
......
FROM --platform=$BUILDPLATFORM golang:1.19.9-alpine3.16 as builder FROM --platform=$BUILDPLATFORM golang:1.20.7-alpine3.18 as builder
ARG VERSION=v0.0.0 ARG VERSION=v0.0.0
...@@ -23,7 +23,7 @@ ARG TARGETOS TARGETARCH ...@@ -23,7 +23,7 @@ ARG TARGETOS TARGETARCH
RUN make op-batcher VERSION="$VERSION" GOOS=$TARGETOS GOARCH=$TARGETARCH RUN make op-batcher VERSION="$VERSION" GOOS=$TARGETOS GOARCH=$TARGETARCH
FROM alpine:3.16 FROM alpine:3.18
COPY --from=builder /app/op-batcher/bin/op-batcher /usr/local/bin COPY --from=builder /app/op-batcher/bin/op-batcher /usr/local/bin
......
...@@ -3,8 +3,8 @@ package compressor_test ...@@ -3,8 +3,8 @@ package compressor_test
import ( import (
"bytes" "bytes"
"compress/zlib" "compress/zlib"
"crypto/rand"
"io" "io"
"math/rand"
"testing" "testing"
"github.com/ethereum-optimism/optimism/op-batcher/compressor" "github.com/ethereum-optimism/optimism/op-batcher/compressor"
......
package state_test package state_test
import ( import (
crand "crypto/rand"
"math/big" "math/big"
"math/rand" "math/rand"
"testing" "testing"
...@@ -47,7 +48,8 @@ func TestCode(t *testing.T) { ...@@ -47,7 +48,8 @@ func TestCode(t *testing.T) {
require.Nil(t, pre) require.Nil(t, pre)
code := make([]byte, rand.Intn(1024)) code := make([]byte, rand.Intn(1024))
rand.Read(code) _, err := crand.Read(code)
require.NoError(t, err)
db.SetCode(addr, code) db.SetCode(addr, code)
......
FROM --platform=$BUILDPLATFORM golang:1.19.0-alpine3.15 as builder FROM --platform=$BUILDPLATFORM golang:1.20.7-alpine3.18 as builder
ARG VERSION=v0.0.0 ARG VERSION=v0.0.0
...@@ -27,7 +27,7 @@ ARG TARGETOS TARGETARCH ...@@ -27,7 +27,7 @@ ARG TARGETOS TARGETARCH
RUN make op-challenger VERSION="$VERSION" GOOS=$TARGETOS GOARCH=$TARGETARCH RUN make op-challenger VERSION="$VERSION" GOOS=$TARGETOS GOARCH=$TARGETARCH
FROM alpine:3.15 FROM alpine:3.18
COPY --from=builder /app/op-challenger/bin/op-challenger /usr/local/bin COPY --from=builder /app/op-challenger/bin/op-challenger /usr/local/bin
......
...@@ -4,6 +4,7 @@ import ( ...@@ -4,6 +4,7 @@ import (
"context" "context"
"fmt" "fmt"
"testing" "testing"
"time"
"github.com/ethereum-optimism/optimism/op-challenger/config" "github.com/ethereum-optimism/optimism/op-challenger/config"
"github.com/ethereum-optimism/optimism/op-node/chaincfg" "github.com/ethereum-optimism/optimism/op-node/chaincfg"
...@@ -21,7 +22,7 @@ var ( ...@@ -21,7 +22,7 @@ var (
cannonBin = "./bin/cannon" cannonBin = "./bin/cannon"
cannonServer = "./bin/op-program" cannonServer = "./bin/op-program"
cannonPreState = "./pre.json" cannonPreState = "./pre.json"
cannonDatadir = "./test_data" datadir = "./test_data"
cannonL2 = "http://example.com:9545" cannonL2 = "http://example.com:9545"
alphabetTrace = "abcdefghijz" alphabetTrace = "abcdefghijz"
agreeWithProposedOutput = "true" agreeWithProposedOutput = "true"
...@@ -44,14 +45,14 @@ func TestLogLevel(t *testing.T) { ...@@ -44,14 +45,14 @@ func TestLogLevel(t *testing.T) {
func TestDefaultCLIOptionsMatchDefaultConfig(t *testing.T) { func TestDefaultCLIOptionsMatchDefaultConfig(t *testing.T) {
cfg := configForArgs(t, addRequiredArgs(config.TraceTypeAlphabet)) cfg := configForArgs(t, addRequiredArgs(config.TraceTypeAlphabet))
defaultCfg := config.NewConfig(common.HexToAddress(gameFactoryAddressValue), l1EthRpc, config.TraceTypeAlphabet, true) defaultCfg := config.NewConfig(common.HexToAddress(gameFactoryAddressValue), l1EthRpc, config.TraceTypeAlphabet, true, datadir)
// Add in the extra CLI options required when using alphabet trace type // Add in the extra CLI options required when using alphabet trace type
defaultCfg.AlphabetTrace = alphabetTrace defaultCfg.AlphabetTrace = alphabetTrace
require.Equal(t, defaultCfg, cfg) require.Equal(t, defaultCfg, cfg)
} }
func TestDefaultConfigIsValid(t *testing.T) { func TestDefaultConfigIsValid(t *testing.T) {
cfg := config.NewConfig(common.HexToAddress(gameFactoryAddressValue), l1EthRpc, config.TraceTypeAlphabet, true) cfg := config.NewConfig(common.HexToAddress(gameFactoryAddressValue), l1EthRpc, config.TraceTypeAlphabet, true, datadir)
// Add in options that are required based on the specific trace type // Add in options that are required based on the specific trace type
// To avoid needing to specify unused options, these aren't included in the params for NewConfig // To avoid needing to specify unused options, these aren't included in the params for NewConfig
cfg.AlphabetTrace = alphabetTrace cfg.AlphabetTrace = alphabetTrace
...@@ -191,18 +192,18 @@ func TestCannonAbsolutePrestate(t *testing.T) { ...@@ -191,18 +192,18 @@ func TestCannonAbsolutePrestate(t *testing.T) {
}) })
} }
func TestCannonDataDir(t *testing.T) { func TestDataDir(t *testing.T) {
t.Run("NotRequiredForAlphabetTrace", func(t *testing.T) { t.Run("RequiredForAlphabetTrace", func(t *testing.T) {
configForArgs(t, addRequiredArgsExcept(config.TraceTypeAlphabet, "--cannon-datadir")) verifyArgsInvalid(t, "flag datadir is required", addRequiredArgsExcept(config.TraceTypeAlphabet, "--datadir"))
}) })
t.Run("Required", func(t *testing.T) { t.Run("RequiredForCannonTrace", func(t *testing.T) {
verifyArgsInvalid(t, "flag cannon-datadir is required", addRequiredArgsExcept(config.TraceTypeCannon, "--cannon-datadir")) verifyArgsInvalid(t, "flag datadir is required", addRequiredArgsExcept(config.TraceTypeCannon, "--datadir"))
}) })
t.Run("Valid", func(t *testing.T) { t.Run("Valid", func(t *testing.T) {
cfg := configForArgs(t, addRequiredArgsExcept(config.TraceTypeCannon, "--cannon-datadir", "--cannon-datadir=/foo/bar/cannon")) cfg := configForArgs(t, addRequiredArgsExcept(config.TraceTypeCannon, "--datadir", "--datadir=/foo/bar/cannon"))
require.Equal(t, "/foo/bar/cannon", cfg.CannonDatadir) require.Equal(t, "/foo/bar/cannon", cfg.Datadir)
}) })
} }
...@@ -233,6 +234,23 @@ func TestCannonSnapshotFreq(t *testing.T) { ...@@ -233,6 +234,23 @@ func TestCannonSnapshotFreq(t *testing.T) {
}) })
} }
func TestGameWindow(t *testing.T) {
t.Run("UsesDefault", func(t *testing.T) {
cfg := configForArgs(t, addRequiredArgs(config.TraceTypeAlphabet))
require.Equal(t, config.DefaultGameWindow, cfg.GameWindow)
})
t.Run("Valid", func(t *testing.T) {
cfg := configForArgs(t, addRequiredArgs(config.TraceTypeAlphabet, "--game-window=1m"))
require.Equal(t, time.Duration(time.Minute), cfg.GameWindow)
})
t.Run("ParsesDefault", func(t *testing.T) {
cfg := configForArgs(t, addRequiredArgs(config.TraceTypeAlphabet, "--game-window=264h"))
require.Equal(t, config.DefaultGameWindow, cfg.GameWindow)
})
}
func TestRequireEitherCannonNetworkOrRollupAndGenesis(t *testing.T) { func TestRequireEitherCannonNetworkOrRollupAndGenesis(t *testing.T) {
verifyArgsInvalid( verifyArgsInvalid(
t, t,
...@@ -335,6 +353,7 @@ func requiredArgs(traceType config.TraceType) map[string]string { ...@@ -335,6 +353,7 @@ func requiredArgs(traceType config.TraceType) map[string]string {
"--l1-eth-rpc": l1EthRpc, "--l1-eth-rpc": l1EthRpc,
"--game-factory-address": gameFactoryAddressValue, "--game-factory-address": gameFactoryAddressValue,
"--trace-type": traceType.String(), "--trace-type": traceType.String(),
"--datadir": datadir,
} }
switch traceType { switch traceType {
case config.TraceTypeAlphabet: case config.TraceTypeAlphabet:
...@@ -344,7 +363,6 @@ func requiredArgs(traceType config.TraceType) map[string]string { ...@@ -344,7 +363,6 @@ func requiredArgs(traceType config.TraceType) map[string]string {
args["--cannon-bin"] = cannonBin args["--cannon-bin"] = cannonBin
args["--cannon-server"] = cannonServer args["--cannon-server"] = cannonServer
args["--cannon-prestate"] = cannonPreState args["--cannon-prestate"] = cannonPreState
args["--cannon-datadir"] = cannonDatadir
args["--cannon-l2"] = cannonL2 args["--cannon-l2"] = cannonL2
} }
return args return args
......
...@@ -3,6 +3,7 @@ package config ...@@ -3,6 +3,7 @@ package config
import ( import (
"errors" "errors"
"fmt" "fmt"
"time"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
...@@ -14,7 +15,7 @@ import ( ...@@ -14,7 +15,7 @@ import (
var ( var (
ErrMissingTraceType = errors.New("missing trace type") ErrMissingTraceType = errors.New("missing trace type")
ErrMissingCannonDatadir = errors.New("missing cannon datadir") ErrMissingDatadir = errors.New("missing datadir")
ErrMissingCannonL2 = errors.New("missing cannon L2") ErrMissingCannonL2 = errors.New("missing cannon L2")
ErrMissingCannonBin = errors.New("missing cannon bin") ErrMissingCannonBin = errors.New("missing cannon bin")
ErrMissingCannonServer = errors.New("missing cannon server") ErrMissingCannonServer = errors.New("missing cannon server")
...@@ -73,7 +74,14 @@ func ValidTraceType(value TraceType) bool { ...@@ -73,7 +74,14 @@ func ValidTraceType(value TraceType) bool {
return false return false
} }
const DefaultCannonSnapshotFreq = uint(1_000_000_000) const (
DefaultCannonSnapshotFreq = uint(1_000_000_000)
// DefaultGameWindow is the default maximum time duration in the past
// that the challenger will look for games to progress.
// The default value is 11 days, which is a 4 day resolution buffer
// plus the 7 day game finalization window.
DefaultGameWindow = time.Duration(11 * 24 * time.Hour)
)
// Config is a well typed config that is parsed from the CLI params. // Config is a well typed config that is parsed from the CLI params.
// This also contains config options for auxiliary services. // This also contains config options for auxiliary services.
...@@ -82,7 +90,9 @@ type Config struct { ...@@ -82,7 +90,9 @@ type Config struct {
L1EthRpc string // L1 RPC Url L1EthRpc string // L1 RPC Url
GameFactoryAddress common.Address // Address of the dispute game factory GameFactoryAddress common.Address // Address of the dispute game factory
GameAllowlist []common.Address // Allowlist of fault game addresses GameAllowlist []common.Address // Allowlist of fault game addresses
GameWindow time.Duration // Maximum time duration to look for games to progress
AgreeWithProposedOutput bool // Temporary config if we agree or disagree with the posted output AgreeWithProposedOutput bool // Temporary config if we agree or disagree with the posted output
Datadir string // Data Directory
TraceType TraceType // Type of trace TraceType TraceType // Type of trace
...@@ -96,7 +106,6 @@ type Config struct { ...@@ -96,7 +106,6 @@ type Config struct {
CannonNetwork string CannonNetwork string
CannonRollupConfigPath string CannonRollupConfigPath string
CannonL2GenesisPath string CannonL2GenesisPath string
CannonDatadir string // Cannon Data Directory
CannonL2 string // L2 RPC Url CannonL2 string // L2 RPC Url
CannonSnapshotFreq uint // Frequency of snapshots to create when executing cannon (in VM instructions) CannonSnapshotFreq uint // Frequency of snapshots to create when executing cannon (in VM instructions)
...@@ -110,6 +119,7 @@ func NewConfig( ...@@ -110,6 +119,7 @@ func NewConfig(
l1EthRpc string, l1EthRpc string,
traceType TraceType, traceType TraceType,
agreeWithProposedOutput bool, agreeWithProposedOutput bool,
datadir string,
) Config { ) Config {
return Config{ return Config{
L1EthRpc: l1EthRpc, L1EthRpc: l1EthRpc,
...@@ -123,7 +133,10 @@ func NewConfig( ...@@ -123,7 +133,10 @@ func NewConfig(
MetricsConfig: opmetrics.DefaultCLIConfig(), MetricsConfig: opmetrics.DefaultCLIConfig(),
PprofConfig: oppprof.DefaultCLIConfig(), PprofConfig: oppprof.DefaultCLIConfig(),
Datadir: datadir,
CannonSnapshotFreq: DefaultCannonSnapshotFreq, CannonSnapshotFreq: DefaultCannonSnapshotFreq,
GameWindow: DefaultGameWindow,
} }
} }
...@@ -137,6 +150,9 @@ func (c Config) Check() error { ...@@ -137,6 +150,9 @@ func (c Config) Check() error {
if c.TraceType == "" { if c.TraceType == "" {
return ErrMissingTraceType return ErrMissingTraceType
} }
if c.Datadir == "" {
return ErrMissingDatadir
}
if c.TraceType == TraceTypeCannon { if c.TraceType == TraceTypeCannon {
if c.CannonBin == "" { if c.CannonBin == "" {
return ErrMissingCannonBin return ErrMissingCannonBin
...@@ -165,9 +181,6 @@ func (c Config) Check() error { ...@@ -165,9 +181,6 @@ func (c Config) Check() error {
if c.CannonAbsolutePreState == "" { if c.CannonAbsolutePreState == "" {
return ErrMissingCannonAbsolutePreState return ErrMissingCannonAbsolutePreState
} }
if c.CannonDatadir == "" {
return ErrMissingCannonDatadir
}
if c.CannonL2 == "" { if c.CannonL2 == "" {
return ErrMissingCannonL2 return ErrMissingCannonL2
} }
......
...@@ -17,13 +17,13 @@ var ( ...@@ -17,13 +17,13 @@ var (
validCannonOpProgramBin = "./bin/op-program" validCannonOpProgramBin = "./bin/op-program"
validCannonNetwork = "mainnet" validCannonNetwork = "mainnet"
validCannonAbsolutPreState = "pre.json" validCannonAbsolutPreState = "pre.json"
validCannonDatadir = "/tmp/cannon" validDatadir = "/tmp/data"
validCannonL2 = "http://localhost:9545" validCannonL2 = "http://localhost:9545"
agreeWithProposedOutput = true agreeWithProposedOutput = true
) )
func validConfig(traceType TraceType) Config { func validConfig(traceType TraceType) Config {
cfg := NewConfig(validGameFactoryAddress, validL1EthRpc, traceType, agreeWithProposedOutput) cfg := NewConfig(validGameFactoryAddress, validL1EthRpc, traceType, agreeWithProposedOutput, validDatadir)
switch traceType { switch traceType {
case TraceTypeAlphabet: case TraceTypeAlphabet:
cfg.AlphabetTrace = validAlphabetTrace cfg.AlphabetTrace = validAlphabetTrace
...@@ -31,7 +31,6 @@ func validConfig(traceType TraceType) Config { ...@@ -31,7 +31,6 @@ func validConfig(traceType TraceType) Config {
cfg.CannonBin = validCannonBin cfg.CannonBin = validCannonBin
cfg.CannonServer = validCannonOpProgramBin cfg.CannonServer = validCannonOpProgramBin
cfg.CannonAbsolutePreState = validCannonAbsolutPreState cfg.CannonAbsolutePreState = validCannonAbsolutPreState
cfg.CannonDatadir = validCannonDatadir
cfg.CannonL2 = validCannonL2 cfg.CannonL2 = validCannonL2
cfg.CannonNetwork = validCannonNetwork cfg.CannonNetwork = validCannonNetwork
} }
...@@ -99,10 +98,10 @@ func TestCannonAbsolutePreStateRequired(t *testing.T) { ...@@ -99,10 +98,10 @@ func TestCannonAbsolutePreStateRequired(t *testing.T) {
require.ErrorIs(t, config.Check(), ErrMissingCannonAbsolutePreState) require.ErrorIs(t, config.Check(), ErrMissingCannonAbsolutePreState)
} }
func TestCannonDatadirRequired(t *testing.T) { func TestDatadirRequired(t *testing.T) {
config := validConfig(TraceTypeCannon) config := validConfig(TraceTypeAlphabet)
config.CannonDatadir = "" config.Datadir = ""
require.ErrorIs(t, config.Check(), ErrMissingCannonDatadir) require.ErrorIs(t, config.Check(), ErrMissingDatadir)
} }
func TestCannonL2Required(t *testing.T) { func TestCannonL2Required(t *testing.T) {
......
...@@ -55,7 +55,7 @@ func TestBuildFaultDefendData(t *testing.T) { ...@@ -55,7 +55,7 @@ func TestBuildFaultDefendData(t *testing.T) {
_, opts, _, contract, err := setupFaultDisputeGame() _, opts, _, contract, err := setupFaultDisputeGame()
require.NoError(t, err) require.NoError(t, err)
responder, _ := newTestFaultResponder(t, false) responder, _ := newTestFaultResponder(t)
data, err := responder.buildFaultDefendData(1, [32]byte{0x02, 0x03}) data, err := responder.buildFaultDefendData(1, [32]byte{0x02, 0x03})
require.NoError(t, err) require.NoError(t, err)
...@@ -72,7 +72,7 @@ func TestBuildFaultAttackData(t *testing.T) { ...@@ -72,7 +72,7 @@ func TestBuildFaultAttackData(t *testing.T) {
_, opts, _, contract, err := setupFaultDisputeGame() _, opts, _, contract, err := setupFaultDisputeGame()
require.NoError(t, err) require.NoError(t, err)
responder, _ := newTestFaultResponder(t, false) responder, _ := newTestFaultResponder(t)
data, err := responder.buildFaultAttackData(1, [32]byte{0x02, 0x03}) data, err := responder.buildFaultAttackData(1, [32]byte{0x02, 0x03})
require.NoError(t, err) require.NoError(t, err)
...@@ -89,7 +89,7 @@ func TestBuildFaultStepData(t *testing.T) { ...@@ -89,7 +89,7 @@ func TestBuildFaultStepData(t *testing.T) {
_, opts, _, contract, err := setupFaultDisputeGame() _, opts, _, contract, err := setupFaultDisputeGame()
require.NoError(t, err) require.NoError(t, err)
responder, _ := newTestFaultResponder(t, false) responder, _ := newTestFaultResponder(t)
data, err := responder.buildStepTxData(types.StepCallData{ data, err := responder.buildStepTxData(types.StepCallData{
ClaimIndex: 2, ClaimIndex: 2,
......
...@@ -13,7 +13,7 @@ import ( ...@@ -13,7 +13,7 @@ import (
// Responder takes a response action & executes. // Responder takes a response action & executes.
// For full op-challenger this means executing the transaction on chain. // For full op-challenger this means executing the transaction on chain.
type Responder interface { type Responder interface {
CanResolve(ctx context.Context) bool CallResolve(ctx context.Context) (types.GameStatus, error)
Resolve(ctx context.Context) error Resolve(ctx context.Context) error
Respond(ctx context.Context, response types.Claim) error Respond(ctx context.Context, response types.Claim) error
Step(ctx context.Context, stepData types.StepCallData) error Step(ctx context.Context, stepData types.StepCallData) error
...@@ -65,10 +65,27 @@ func (a *Agent) Act(ctx context.Context) error { ...@@ -65,10 +65,27 @@ func (a *Agent) Act(ctx context.Context) error {
return nil return nil
} }
// shouldResolve returns true if the agent should resolve the game.
// This method will return false if the game is still in progress.
func (a *Agent) shouldResolve(ctx context.Context, status types.GameStatus) bool {
expected := types.GameStatusDefenderWon
if a.agreeWithProposedOutput {
expected = types.GameStatusChallengerWon
}
if expected != status {
a.log.Warn("Game will be lost", "expected", expected, "actual", status)
}
return expected == status
}
// tryResolve resolves the game if it is in a terminal state // tryResolve resolves the game if it is in a terminal state
// and returns true if the game resolves successfully. // and returns true if the game resolves successfully.
func (a *Agent) tryResolve(ctx context.Context) bool { func (a *Agent) tryResolve(ctx context.Context) bool {
if !a.responder.CanResolve(ctx) { status, err := a.responder.CallResolve(ctx)
if err != nil {
return false
}
if !a.shouldResolve(ctx, status) {
return false return false
} }
a.log.Info("Resolving game") a.log.Info("Resolving game")
......
package fault
import (
"context"
"testing"
"github.com/ethereum/go-ethereum/log"
"github.com/stretchr/testify/require"
"github.com/ethereum-optimism/optimism/op-challenger/fault/types"
"github.com/ethereum-optimism/optimism/op-node/testlog"
)
// TestShouldResolve tests the resolution logic.
func TestShouldResolve(t *testing.T) {
log := testlog.Logger(t, log.LvlCrit)
t.Run("AgreeWithProposedOutput", func(t *testing.T) {
agent := NewAgent(nil, 0, nil, nil, nil, true, log)
require.False(t, agent.shouldResolve(context.Background(), types.GameStatusDefenderWon))
require.True(t, agent.shouldResolve(context.Background(), types.GameStatusChallengerWon))
require.False(t, agent.shouldResolve(context.Background(), types.GameStatusInProgress))
})
t.Run("DisagreeWithProposedOutput", func(t *testing.T) {
agent := NewAgent(nil, 0, nil, nil, nil, false, log)
require.True(t, agent.shouldResolve(context.Background(), types.GameStatusDefenderWon))
require.False(t, agent.shouldResolve(context.Background(), types.GameStatusChallengerWon))
require.False(t, agent.shouldResolve(context.Background(), types.GameStatusInProgress))
})
}
...@@ -8,7 +8,6 @@ import ( ...@@ -8,7 +8,6 @@ import (
"github.com/ethereum-optimism/optimism/op-challenger/fault/types" "github.com/ethereum-optimism/optimism/op-challenger/fault/types"
"github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/ethclient"
) )
type FaultDisputeGameCaller interface { type FaultDisputeGameCaller interface {
...@@ -26,7 +25,7 @@ func NewFaultCaller(caller FaultDisputeGameCaller) *FaultCaller { ...@@ -26,7 +25,7 @@ func NewFaultCaller(caller FaultDisputeGameCaller) *FaultCaller {
} }
} }
func NewFaultCallerFromBindings(fdgAddr common.Address, client *ethclient.Client) (*FaultCaller, error) { func NewFaultCallerFromBindings(fdgAddr common.Address, client bind.ContractCaller) (*FaultCaller, error) {
caller, err := bindings.NewFaultDisputeGameCaller(fdgAddr, client) caller, err := bindings.NewFaultDisputeGameCaller(fdgAddr, client)
if err != nil { if err != nil {
return nil, err return nil, err
......
...@@ -21,10 +21,9 @@ const execTestCannonPrestate = "/foo/pre.json" ...@@ -21,10 +21,9 @@ const execTestCannonPrestate = "/foo/pre.json"
func TestGenerateProof(t *testing.T) { func TestGenerateProof(t *testing.T) {
input := "starting.json" input := "starting.json"
cfg := config.NewConfig(common.Address{0xbb}, "http://localhost:8888", config.TraceTypeCannon, true)
tempDir := t.TempDir() tempDir := t.TempDir()
dir := filepath.Join(tempDir, "gameDir") dir := filepath.Join(tempDir, "gameDir")
cfg.CannonDatadir = tempDir cfg := config.NewConfig(common.Address{0xbb}, "http://localhost:8888", config.TraceTypeCannon, true, tempDir)
cfg.CannonAbsolutePreState = "pre.json" cfg.CannonAbsolutePreState = "pre.json"
cfg.CannonBin = "./bin/cannon" cfg.CannonBin = "./bin/cannon"
cfg.CannonServer = "./bin/op-program" cfg.CannonServer = "./bin/op-program"
......
...@@ -50,7 +50,7 @@ type CannonTraceProvider struct { ...@@ -50,7 +50,7 @@ type CannonTraceProvider struct {
lastProof *proofData lastProof *proofData
} }
func NewTraceProvider(ctx context.Context, logger log.Logger, cfg *config.Config, l1Client bind.ContractCaller, gameAddr common.Address) (*CannonTraceProvider, error) { func NewTraceProvider(ctx context.Context, logger log.Logger, cfg *config.Config, l1Client bind.ContractCaller, dir string, gameAddr common.Address) (*CannonTraceProvider, error) {
l2Client, err := ethclient.DialContext(ctx, cfg.CannonL2) l2Client, err := ethclient.DialContext(ctx, cfg.CannonL2)
if err != nil { if err != nil {
return nil, fmt.Errorf("dial l2 client %v: %w", cfg.CannonL2, err) return nil, fmt.Errorf("dial l2 client %v: %w", cfg.CannonL2, err)
...@@ -64,11 +64,10 @@ func NewTraceProvider(ctx context.Context, logger log.Logger, cfg *config.Config ...@@ -64,11 +64,10 @@ func NewTraceProvider(ctx context.Context, logger log.Logger, cfg *config.Config
if err != nil { if err != nil {
return nil, fmt.Errorf("fetch local game inputs: %w", err) return nil, fmt.Errorf("fetch local game inputs: %w", err)
} }
return NewTraceProviderFromInputs(logger, cfg, gameAddr.Hex(), localInputs), nil return NewTraceProviderFromInputs(logger, cfg, localInputs, dir), nil
} }
func NewTraceProviderFromInputs(logger log.Logger, cfg *config.Config, gameDirName string, localInputs LocalGameInputs) *CannonTraceProvider { func NewTraceProviderFromInputs(logger log.Logger, cfg *config.Config, localInputs LocalGameInputs, dir string) *CannonTraceProvider {
dir := filepath.Join(cfg.CannonDatadir, gameDirName)
return &CannonTraceProvider{ return &CannonTraceProvider{
logger: logger, logger: logger,
dir: dir, dir: dir,
......
...@@ -11,7 +11,6 @@ import ( ...@@ -11,7 +11,6 @@ import (
"testing" "testing"
"github.com/ethereum-optimism/optimism/cannon/mipsevm" "github.com/ethereum-optimism/optimism/cannon/mipsevm"
"github.com/ethereum-optimism/optimism/op-challenger/config"
"github.com/ethereum-optimism/optimism/op-challenger/fault/types" "github.com/ethereum-optimism/optimism/op-challenger/fault/types"
"github.com/ethereum-optimism/optimism/op-node/testlog" "github.com/ethereum-optimism/optimism/op-node/testlog"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
...@@ -189,21 +188,6 @@ func TestAbsolutePreState(t *testing.T) { ...@@ -189,21 +188,6 @@ func TestAbsolutePreState(t *testing.T) {
}) })
} }
func TestUseGameSpecificSubdir(t *testing.T) {
tempDir := t.TempDir()
dataDir := filepath.Join(tempDir, "data")
setupPreState(t, tempDir, "state.json")
logger := testlog.Logger(t, log.LvlInfo)
cfg := &config.Config{
CannonAbsolutePreState: filepath.Join(tempDir, "state.json"),
CannonDatadir: dataDir,
}
gameDirName := "gameSubdir"
localInputs := LocalGameInputs{}
provider := NewTraceProviderFromInputs(logger, cfg, gameDirName, localInputs)
require.Equal(t, filepath.Join(dataDir, gameDirName), provider.dir, "should use game specific subdir")
}
func setupPreState(t *testing.T, dataDir string, filename string) { func setupPreState(t *testing.T, dataDir string, filename string) {
srcDir := filepath.Join("test_data") srcDir := filepath.Join("test_data")
path := filepath.Join(srcDir, filename) path := filepath.Join(srcDir, filename)
......
package fault
import (
"fmt"
"os"
"path/filepath"
"strings"
"github.com/ethereum/go-ethereum/common"
"github.com/hashicorp/go-multierror"
"golang.org/x/exp/slices"
)
const gameDirPrefix = "game-"
// diskManager coordinates
type diskManager struct {
datadir string
}
func newDiskManager(dir string) *diskManager {
return &diskManager{datadir: dir}
}
func (d *diskManager) DirForGame(addr common.Address) string {
return filepath.Join(d.datadir, gameDirPrefix+addr.Hex())
}
func (d *diskManager) RemoveAllExcept(keep []common.Address) error {
entries, err := os.ReadDir(d.datadir)
if err != nil {
return fmt.Errorf("failed to list directory: %w", err)
}
var result error
for _, entry := range entries {
if !entry.IsDir() || !strings.HasPrefix(entry.Name(), gameDirPrefix) {
// Skip files and directories that don't have the game directory prefix.
// While random content shouldn't be in our datadir, we want to avoid
// deleting things like OS generated files.
continue
}
name := entry.Name()[len(gameDirPrefix):]
addr := common.HexToAddress(name)
if addr == (common.Address{}) {
// Couldn't parse the directory name to an address so mustn't be a game directory
continue
}
if slices.Contains(keep, addr) {
// We need to preserve this data
continue
}
if err := os.RemoveAll(filepath.Join(d.datadir, entry.Name())); err != nil {
result = multierror.Append(result, err)
}
}
return result
}
package fault
import (
"os"
"path/filepath"
"testing"
"github.com/ethereum/go-ethereum/common"
"github.com/stretchr/testify/require"
)
func TestDiskManager_DirForGame(t *testing.T) {
baseDir := t.TempDir()
addr := common.Address{0x53}
disk := newDiskManager(baseDir)
result := disk.DirForGame(addr)
require.Equal(t, filepath.Join(baseDir, gameDirPrefix+addr.Hex()), result)
}
func TestDiskManager_RemoveAllExcept(t *testing.T) {
baseDir := t.TempDir()
keep := common.Address{0x53}
delete := common.Address{0xaa}
disk := newDiskManager(baseDir)
keepDir := disk.DirForGame(keep)
deleteDir := disk.DirForGame(delete)
unexpectedFile := filepath.Join(baseDir, "file.txt")
require.NoError(t, os.WriteFile(unexpectedFile, []byte("test"), 0644))
unexpectedDir := filepath.Join(baseDir, "notagame")
require.NoError(t, os.MkdirAll(unexpectedDir, 0777))
invalidHexDir := filepath.Join(baseDir, gameDirPrefix+"0xNOPE")
require.NoError(t, os.MkdirAll(invalidHexDir, 0777))
populateDir := func(dir string) []string {
require.NoError(t, os.MkdirAll(dir, 0777))
file1 := filepath.Join(dir, "test.txt")
require.NoError(t, os.WriteFile(file1, []byte("foo"), 0644))
nestedDirs := filepath.Join(dir, "subdir", "deep")
require.NoError(t, os.MkdirAll(nestedDirs, 0777))
file2 := filepath.Join(nestedDirs, ".foo.txt")
require.NoError(t, os.WriteFile(file2, []byte("foo"), 0644))
return []string{file1, file2}
}
keepFiles := populateDir(keepDir)
populateDir(deleteDir)
require.NoError(t, disk.RemoveAllExcept([]common.Address{keep}))
require.NoDirExists(t, deleteDir, "should have deleted directory")
for _, file := range keepFiles {
require.FileExists(t, file, "should have kept file for active game")
}
require.FileExists(t, unexpectedFile, "should not delete unexpected file")
require.DirExists(t, unexpectedDir, "should not delete unexpected dir")
require.DirExists(t, invalidHexDir, "should not delete dir with invalid address")
}
...@@ -43,7 +43,7 @@ func NewGameLoader(caller MinimalDisputeGameFactoryCaller) *gameLoader { ...@@ -43,7 +43,7 @@ func NewGameLoader(caller MinimalDisputeGameFactoryCaller) *gameLoader {
} }
// FetchAllGamesAtBlock fetches all dispute games from the factory at a given block number. // FetchAllGamesAtBlock fetches all dispute games from the factory at a given block number.
func (l *gameLoader) FetchAllGamesAtBlock(ctx context.Context, blockNumber *big.Int) ([]FaultDisputeGame, error) { func (l *gameLoader) FetchAllGamesAtBlock(ctx context.Context, earliestTimestamp uint64, blockNumber *big.Int) ([]FaultDisputeGame, error) {
if blockNumber == nil { if blockNumber == nil {
return nil, ErrMissingBlockNumber return nil, ErrMissingBlockNumber
} }
...@@ -56,14 +56,19 @@ func (l *gameLoader) FetchAllGamesAtBlock(ctx context.Context, blockNumber *big. ...@@ -56,14 +56,19 @@ func (l *gameLoader) FetchAllGamesAtBlock(ctx context.Context, blockNumber *big.
return nil, fmt.Errorf("failed to fetch game count: %w", err) return nil, fmt.Errorf("failed to fetch game count: %w", err)
} }
games := make([]FaultDisputeGame, gameCount.Uint64()) games := make([]FaultDisputeGame, 0)
for i := uint64(0); i < gameCount.Uint64(); i++ { if gameCount.Uint64() == 0 {
game, err := l.caller.GameAtIndex(callOpts, big.NewInt(int64(i))) return games, nil
}
for i := gameCount.Uint64(); i > 0; i-- {
game, err := l.caller.GameAtIndex(callOpts, big.NewInt(int64(i-1)))
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to fetch game at index %d: %w", i, err) return nil, fmt.Errorf("failed to fetch game at index %d: %w", i, err)
} }
if game.Timestamp < earliestTimestamp {
games[i] = game break
}
games = append(games, game)
} }
return games, nil return games, nil
......
...@@ -25,6 +25,7 @@ func TestGameLoader_FetchAllGames(t *testing.T) { ...@@ -25,6 +25,7 @@ func TestGameLoader_FetchAllGames(t *testing.T) {
tests := []struct { tests := []struct {
name string name string
caller *mockMinimalDisputeGameFactoryCaller caller *mockMinimalDisputeGameFactoryCaller
earliest uint64
blockNumber *big.Int blockNumber *big.Int
expectedErr error expectedErr error
expectedLen int expectedLen int
...@@ -33,35 +34,36 @@ func TestGameLoader_FetchAllGames(t *testing.T) { ...@@ -33,35 +34,36 @@ func TestGameLoader_FetchAllGames(t *testing.T) {
name: "success", name: "success",
caller: newMockMinimalDisputeGameFactoryCaller(10, false, false), caller: newMockMinimalDisputeGameFactoryCaller(10, false, false),
blockNumber: big.NewInt(1), blockNumber: big.NewInt(1),
expectedErr: nil,
expectedLen: 10, expectedLen: 10,
}, },
{
name: "expired game ignored",
caller: newMockMinimalDisputeGameFactoryCaller(10, false, false),
earliest: 500,
blockNumber: big.NewInt(1),
expectedLen: 5,
},
{ {
name: "game count error", name: "game count error",
caller: newMockMinimalDisputeGameFactoryCaller(10, true, false), caller: newMockMinimalDisputeGameFactoryCaller(10, true, false),
blockNumber: big.NewInt(1), blockNumber: big.NewInt(1),
expectedErr: gameCountErr, expectedErr: gameCountErr,
expectedLen: 0,
}, },
{ {
name: "game index error", name: "game index error",
caller: newMockMinimalDisputeGameFactoryCaller(10, false, true), caller: newMockMinimalDisputeGameFactoryCaller(10, false, true),
blockNumber: big.NewInt(1), blockNumber: big.NewInt(1),
expectedErr: gameIndexErr, expectedErr: gameIndexErr,
expectedLen: 0,
}, },
{ {
name: "no games", name: "no games",
caller: newMockMinimalDisputeGameFactoryCaller(0, false, false), caller: newMockMinimalDisputeGameFactoryCaller(0, false, false),
blockNumber: big.NewInt(1), blockNumber: big.NewInt(1),
expectedErr: nil,
expectedLen: 0,
}, },
{ {
name: "missing block number", name: "missing block number",
caller: newMockMinimalDisputeGameFactoryCaller(0, false, false), caller: newMockMinimalDisputeGameFactoryCaller(0, false, false),
expectedErr: ErrMissingBlockNumber, expectedErr: ErrMissingBlockNumber,
expectedLen: 0,
}, },
} }
...@@ -72,10 +74,11 @@ func TestGameLoader_FetchAllGames(t *testing.T) { ...@@ -72,10 +74,11 @@ func TestGameLoader_FetchAllGames(t *testing.T) {
t.Parallel() t.Parallel()
loader := NewGameLoader(test.caller) loader := NewGameLoader(test.caller)
games, err := loader.FetchAllGamesAtBlock(context.Background(), test.blockNumber) games, err := loader.FetchAllGamesAtBlock(context.Background(), test.earliest, test.blockNumber)
require.ErrorIs(t, err, test.expectedErr) require.ErrorIs(t, err, test.expectedErr)
require.Len(t, games, test.expectedLen) require.Len(t, games, test.expectedLen)
expectedGames := test.caller.games expectedGames := test.caller.games
expectedGames = expectedGames[len(expectedGames)-test.expectedLen:]
if test.expectedErr != nil { if test.expectedErr != nil {
expectedGames = make([]FaultDisputeGame, 0) expectedGames = make([]FaultDisputeGame, 0)
} }
...@@ -90,7 +93,7 @@ func generateMockGames(count uint64) []FaultDisputeGame { ...@@ -90,7 +93,7 @@ func generateMockGames(count uint64) []FaultDisputeGame {
for i := uint64(0); i < count; i++ { for i := uint64(0); i < count; i++ {
games[i] = FaultDisputeGame{ games[i] = FaultDisputeGame{
Proxy: common.BigToAddress(big.NewInt(int64(i))), Proxy: common.BigToAddress(big.NewInt(int64(i))),
Timestamp: i, Timestamp: i * 100,
} }
} }
......
...@@ -15,29 +15,47 @@ type gamePlayer interface { ...@@ -15,29 +15,47 @@ type gamePlayer interface {
ProgressGame(ctx context.Context) bool ProgressGame(ctx context.Context) bool
} }
type playerCreator func(address common.Address) (gamePlayer, error) type playerCreator func(address common.Address, dir string) (gamePlayer, error)
type blockNumberFetcher func(ctx context.Context) (uint64, error) type blockNumberFetcher func(ctx context.Context) (uint64, error)
// gameSource loads information about the games available to play // gameSource loads information about the games available to play
type gameSource interface { type gameSource interface {
FetchAllGamesAtBlock(ctx context.Context, blockNumber *big.Int) ([]FaultDisputeGame, error) FetchAllGamesAtBlock(ctx context.Context, earliest uint64, blockNumber *big.Int) ([]FaultDisputeGame, error)
}
type gameDiskAllocator interface {
DirForGame(common.Address) string
RemoveAllExcept([]common.Address) error
} }
type gameMonitor struct { type gameMonitor struct {
logger log.Logger logger log.Logger
clock clock.Clock clock clock.Clock
diskManager gameDiskAllocator
source gameSource source gameSource
gameWindow time.Duration
createPlayer playerCreator createPlayer playerCreator
fetchBlockNumber blockNumberFetcher fetchBlockNumber blockNumberFetcher
allowedGames []common.Address allowedGames []common.Address
players map[common.Address]gamePlayer players map[common.Address]gamePlayer
} }
func newGameMonitor(logger log.Logger, cl clock.Clock, fetchBlockNumber blockNumberFetcher, allowedGames []common.Address, source gameSource, createGame playerCreator) *gameMonitor { func newGameMonitor(
logger log.Logger,
gameWindow time.Duration,
cl clock.Clock,
disk gameDiskAllocator,
fetchBlockNumber blockNumberFetcher,
allowedGames []common.Address,
source gameSource,
createGame playerCreator,
) *gameMonitor {
return &gameMonitor{ return &gameMonitor{
logger: logger, logger: logger,
clock: cl, clock: cl,
diskManager: disk,
source: source, source: source,
gameWindow: gameWindow,
createPlayer: createGame, createPlayer: createGame,
fetchBlockNumber: fetchBlockNumber, fetchBlockNumber: fetchBlockNumber,
allowedGames: allowedGames, allowedGames: allowedGames,
...@@ -57,26 +75,59 @@ func (m *gameMonitor) allowedGame(game common.Address) bool { ...@@ -57,26 +75,59 @@ func (m *gameMonitor) allowedGame(game common.Address) bool {
return false return false
} }
func (m *gameMonitor) minGameTimestamp() uint64 {
if m.gameWindow.Seconds() == 0 {
return 0
}
// time: "To compute t-d for a duration d, use t.Add(-d)."
// https://pkg.go.dev/time#Time.Sub
if m.clock.Now().Unix() > int64(m.gameWindow.Seconds()) {
return uint64(m.clock.Now().Add(-m.gameWindow).Unix())
}
return 0
}
func (m *gameMonitor) progressGames(ctx context.Context) error { func (m *gameMonitor) progressGames(ctx context.Context) error {
blockNum, err := m.fetchBlockNumber(ctx) blockNum, err := m.fetchBlockNumber(ctx)
if err != nil { if err != nil {
return fmt.Errorf("failed to load current block number: %w", err) return fmt.Errorf("failed to load current block number: %w", err)
} }
games, err := m.source.FetchAllGamesAtBlock(ctx, new(big.Int).SetUint64(blockNum)) games, err := m.source.FetchAllGamesAtBlock(ctx, m.minGameTimestamp(), new(big.Int).SetUint64(blockNum))
if err != nil { if err != nil {
return fmt.Errorf("failed to load games: %w", err) return fmt.Errorf("failed to load games: %w", err)
} }
requiredGames := make(map[common.Address]bool)
var keepGameData []common.Address
for _, game := range games { for _, game := range games {
if !m.allowedGame(game.Proxy) { if !m.allowedGame(game.Proxy) {
m.logger.Debug("Skipping game not on allow list", "game", game.Proxy) m.logger.Debug("Skipping game not on allow list", "game", game.Proxy)
continue continue
} }
requiredGames[game.Proxy] = true
player, err := m.fetchOrCreateGamePlayer(game) player, err := m.fetchOrCreateGamePlayer(game)
if err != nil { if err != nil {
m.logger.Error("Error while progressing game", "game", game.Proxy, "err", err) m.logger.Error("Error while progressing game", "game", game.Proxy, "err", err)
continue continue
} }
player.ProgressGame(ctx) done := player.ProgressGame(ctx)
if !done {
// We only keep resources on disk for games that are incomplete.
// Games that are complete have their data removed as soon as possible to save disk space.
// We keep the player in memory to avoid recreating it on every update but will no longer
// need the resources on disk because there are no further actions required on the game.
keepGameData = append(keepGameData, game.Proxy)
}
}
if err := m.diskManager.RemoveAllExcept(keepGameData); err != nil {
m.logger.Error("Unable to cleanup game data", "err", err)
}
// Remove the player for any game that's no longer being returned from the list of active games
for addr := range m.players {
if _, ok := requiredGames[addr]; ok {
// Game still required
continue
}
delete(m.players, addr)
} }
return nil return nil
} }
...@@ -85,7 +136,7 @@ func (m *gameMonitor) fetchOrCreateGamePlayer(gameData FaultDisputeGame) (gamePl ...@@ -85,7 +136,7 @@ func (m *gameMonitor) fetchOrCreateGamePlayer(gameData FaultDisputeGame) (gamePl
if player, ok := m.players[gameData.Proxy]; ok { if player, ok := m.players[gameData.Proxy]; ok {
return player, nil return player, nil
} }
player, err := m.createPlayer(gameData.Proxy) player, err := m.createPlayer(gameData.Proxy, m.diskManager.DirForGame(gameData.Proxy))
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to create game player %v: %w", gameData.Proxy, err) return nil, fmt.Errorf("failed to create game player %v: %w", gameData.Proxy, err)
} }
......
...@@ -4,17 +4,45 @@ import ( ...@@ -4,17 +4,45 @@ import (
"context" "context"
"math/big" "math/big"
"testing" "testing"
"time"
"github.com/ethereum-optimism/optimism/op-service/clock" "github.com/ethereum-optimism/optimism/op-service/clock"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"golang.org/x/exp/slices"
"github.com/ethereum-optimism/optimism/op-node/testlog" "github.com/ethereum-optimism/optimism/op-node/testlog"
) )
func TestMonitorMinGameTimestamp(t *testing.T) {
t.Parallel()
t.Run("zero game window returns zero", func(t *testing.T) {
monitor, _, _, _ := setupMonitorTest(t, []common.Address{})
monitor.gameWindow = time.Duration(0)
require.Equal(t, monitor.minGameTimestamp(), uint64(0))
})
t.Run("non-zero game window with zero clock", func(t *testing.T) {
monitor, _, _, _ := setupMonitorTest(t, []common.Address{})
monitor.gameWindow = time.Minute
monitor.clock = clock.NewDeterministicClock(time.Unix(0, 0))
require.Equal(t, monitor.minGameTimestamp(), uint64(0))
})
t.Run("minimum computed correctly", func(t *testing.T) {
monitor, _, _, _ := setupMonitorTest(t, []common.Address{})
monitor.gameWindow = time.Minute
frozen := time.Unix(int64(time.Hour.Seconds()), 0)
monitor.clock = clock.NewDeterministicClock(frozen)
expected := uint64(frozen.Add(-time.Minute).Unix())
require.Equal(t, monitor.minGameTimestamp(), expected)
})
}
func TestMonitorExitsWhenContextDone(t *testing.T) { func TestMonitorExitsWhenContextDone(t *testing.T) {
monitor, _, _ := setupMonitorTest(t, []common.Address{common.Address{}}) monitor, _, _, _ := setupMonitorTest(t, []common.Address{{}})
ctx, cancel := context.WithCancel(context.Background()) ctx, cancel := context.WithCancel(context.Background())
cancel() cancel()
err := monitor.MonitorGames(ctx) err := monitor.MonitorGames(ctx)
...@@ -22,7 +50,7 @@ func TestMonitorExitsWhenContextDone(t *testing.T) { ...@@ -22,7 +50,7 @@ func TestMonitorExitsWhenContextDone(t *testing.T) {
} }
func TestMonitorCreateAndProgressGameAgents(t *testing.T) { func TestMonitorCreateAndProgressGameAgents(t *testing.T) {
monitor, source, games := setupMonitorTest(t, []common.Address{}) monitor, source, games, _ := setupMonitorTest(t, []common.Address{})
addr1 := common.Address{0xaa} addr1 := common.Address{0xaa}
addr2 := common.Address{0xbb} addr2 := common.Address{0xbb}
...@@ -55,7 +83,7 @@ func TestMonitorCreateAndProgressGameAgents(t *testing.T) { ...@@ -55,7 +83,7 @@ func TestMonitorCreateAndProgressGameAgents(t *testing.T) {
func TestMonitorOnlyCreateSpecifiedGame(t *testing.T) { func TestMonitorOnlyCreateSpecifiedGame(t *testing.T) {
addr1 := common.Address{0xaa} addr1 := common.Address{0xaa}
addr2 := common.Address{0xbb} addr2 := common.Address{0xbb}
monitor, source, games := setupMonitorTest(t, []common.Address{addr2}) monitor, source, games, _ := setupMonitorTest(t, []common.Address{addr2})
source.games = []FaultDisputeGame{ source.games = []FaultDisputeGame{
{ {
...@@ -77,7 +105,82 @@ func TestMonitorOnlyCreateSpecifiedGame(t *testing.T) { ...@@ -77,7 +105,82 @@ func TestMonitorOnlyCreateSpecifiedGame(t *testing.T) {
require.Equal(t, 1, games.created[addr2].progressCount) require.Equal(t, 1, games.created[addr2].progressCount)
} }
func setupMonitorTest(t *testing.T, allowedGames []common.Address) (*gameMonitor, *stubGameSource, *createdGames) { func TestDeletePlayersWhenNoLongerInListOfGames(t *testing.T) {
addr1 := common.Address{0xaa}
addr2 := common.Address{0xbb}
monitor, source, games, _ := setupMonitorTest(t, nil)
allGames := []FaultDisputeGame{
{
Proxy: addr1,
Timestamp: 9999,
},
{
Proxy: addr2,
Timestamp: 9999,
},
}
source.games = allGames
require.NoError(t, monitor.progressGames(context.Background()))
require.Len(t, games.created, 2)
require.Contains(t, games.created, addr1)
require.Contains(t, games.created, addr2)
// First game is now old enough it's not returned in the list of active games
source.games = source.games[1:]
require.NoError(t, monitor.progressGames(context.Background()))
require.Len(t, games.created, 2)
require.Contains(t, games.created, addr1)
require.Contains(t, games.created, addr2)
// Forget that we created the first game so it can be recreated if needed
delete(games.created, addr1)
// First game now reappears (inexplicably but usefully for our testing)
source.games = allGames
require.NoError(t, monitor.progressGames(context.Background()))
// A new player is created for it because the original was deleted
require.Len(t, games.created, 2)
require.Contains(t, games.created, addr1)
require.Contains(t, games.created, addr2)
require.Equal(t, 1, games.created[addr1].progressCount)
}
func TestCleanupResourcesOfCompletedGames(t *testing.T) {
addr1 := common.Address{0xaa}
addr2 := common.Address{0xbb}
monitor, source, games, disk := setupMonitorTest(t, []common.Address{})
games.createCompleted = addr1
source.games = []FaultDisputeGame{
{
Proxy: addr1,
Timestamp: 1999,
},
{
Proxy: addr2,
Timestamp: 9999,
},
}
err := monitor.progressGames(context.Background())
require.NoError(t, err)
require.Len(t, games.created, 2, "should create game agents")
require.Contains(t, games.created, addr1)
require.Contains(t, games.created, addr2)
require.Equal(t, 1, games.created[addr1].progressCount)
require.Equal(t, 1, games.created[addr2].progressCount)
require.Contains(t, disk.gameDirExists, addr1, "should have allocated a game dir for game 1")
require.False(t, disk.gameDirExists[addr1], "should have then deleted the game 1 dir")
require.Contains(t, disk.gameDirExists, addr2, "should have allocated a game dir for game 2")
require.True(t, disk.gameDirExists[addr2], "should not have deleted the game 2 dir")
}
func setupMonitorTest(t *testing.T, allowedGames []common.Address) (*gameMonitor, *stubGameSource, *createdGames, *stubDiskManager) {
logger := testlog.Logger(t, log.LvlDebug) logger := testlog.Logger(t, log.LvlDebug)
source := &stubGameSource{} source := &stubGameSource{}
games := &createdGames{ games := &createdGames{
...@@ -87,15 +190,18 @@ func setupMonitorTest(t *testing.T, allowedGames []common.Address) (*gameMonitor ...@@ -87,15 +190,18 @@ func setupMonitorTest(t *testing.T, allowedGames []common.Address) (*gameMonitor
fetchBlockNum := func(ctx context.Context) (uint64, error) { fetchBlockNum := func(ctx context.Context) (uint64, error) {
return 1234, nil return 1234, nil
} }
monitor := newGameMonitor(logger, clock.SystemClock, fetchBlockNum, allowedGames, source, games.CreateGame) disk := &stubDiskManager{
return monitor, source, games gameDirExists: make(map[common.Address]bool),
}
monitor := newGameMonitor(logger, time.Duration(0), clock.SystemClock, disk, fetchBlockNum, allowedGames, source, games.CreateGame)
return monitor, source, games, disk
} }
type stubGameSource struct { type stubGameSource struct {
games []FaultDisputeGame games []FaultDisputeGame
} }
func (s *stubGameSource) FetchAllGamesAtBlock(ctx context.Context, blockNumber *big.Int) ([]FaultDisputeGame, error) { func (s *stubGameSource) FetchAllGamesAtBlock(ctx context.Context, earliest uint64, blockNumber *big.Int) ([]FaultDisputeGame, error) {
return s.games, nil return s.games, nil
} }
...@@ -103,6 +209,7 @@ type stubGame struct { ...@@ -103,6 +209,7 @@ type stubGame struct {
addr common.Address addr common.Address
progressCount int progressCount int
done bool done bool
dir string
} }
func (g *stubGame) ProgressGame(ctx context.Context) bool { func (g *stubGame) ProgressGame(ctx context.Context) bool {
...@@ -111,15 +218,36 @@ func (g *stubGame) ProgressGame(ctx context.Context) bool { ...@@ -111,15 +218,36 @@ func (g *stubGame) ProgressGame(ctx context.Context) bool {
} }
type createdGames struct { type createdGames struct {
t *testing.T t *testing.T
created map[common.Address]*stubGame createCompleted common.Address
created map[common.Address]*stubGame
} }
func (c *createdGames) CreateGame(addr common.Address) (gamePlayer, error) { func (c *createdGames) CreateGame(addr common.Address, dir string) (gamePlayer, error) {
if _, exists := c.created[addr]; exists { if _, exists := c.created[addr]; exists {
c.t.Fatalf("game %v already exists", addr) c.t.Fatalf("game %v already exists", addr)
} }
game := &stubGame{addr: addr} game := &stubGame{
addr: addr,
done: addr == c.createCompleted,
dir: dir,
}
c.created[addr] = game c.created[addr] = game
return game, nil return game, nil
} }
type stubDiskManager struct {
gameDirExists map[common.Address]bool
}
func (s *stubDiskManager) DirForGame(addr common.Address) string {
s.gameDirExists[addr] = true
return addr.Hex()
}
func (s *stubDiskManager) RemoveAllExcept(addrs []common.Address) error {
for address := range s.gameDirExists {
s.gameDirExists[address] = slices.Contains(addrs, address)
}
return nil
}
...@@ -10,8 +10,8 @@ import ( ...@@ -10,8 +10,8 @@ import (
"github.com/ethereum-optimism/optimism/op-challenger/fault/cannon" "github.com/ethereum-optimism/optimism/op-challenger/fault/cannon"
"github.com/ethereum-optimism/optimism/op-challenger/fault/types" "github.com/ethereum-optimism/optimism/op-challenger/fault/types"
"github.com/ethereum-optimism/optimism/op-service/txmgr" "github.com/ethereum-optimism/optimism/op-service/txmgr"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/ethclient"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
) )
...@@ -29,15 +29,18 @@ type GamePlayer struct { ...@@ -29,15 +29,18 @@ type GamePlayer struct {
agreeWithProposedOutput bool agreeWithProposedOutput bool
caller GameInfo caller GameInfo
logger log.Logger logger log.Logger
completed bool
} }
func NewGamePlayer( func NewGamePlayer(
ctx context.Context, ctx context.Context,
logger log.Logger, logger log.Logger,
cfg *config.Config, cfg *config.Config,
dir string,
addr common.Address, addr common.Address,
txMgr txmgr.TxManager, txMgr txmgr.TxManager,
client *ethclient.Client, client bind.ContractCaller,
) (*GamePlayer, error) { ) (*GamePlayer, error) {
logger = logger.New("game", addr) logger = logger.New("game", addr)
contract, err := bindings.NewFaultDisputeGameCaller(addr, client) contract, err := bindings.NewFaultDisputeGameCaller(addr, client)
...@@ -56,10 +59,11 @@ func NewGamePlayer( ...@@ -56,10 +59,11 @@ func NewGamePlayer(
var updater types.OracleUpdater var updater types.OracleUpdater
switch cfg.TraceType { switch cfg.TraceType {
case config.TraceTypeCannon: case config.TraceTypeCannon:
provider, err = cannon.NewTraceProvider(ctx, logger, cfg, client, addr) cannonProvider, err := cannon.NewTraceProvider(ctx, logger, cfg, client, dir, addr)
if err != nil { if err != nil {
return nil, fmt.Errorf("create cannon trace provider: %w", err) return nil, fmt.Errorf("create cannon trace provider: %w", err)
} }
provider = cannonProvider
updater, err = cannon.NewOracleUpdater(ctx, logger, txMgr, addr, client) updater, err = cannon.NewOracleUpdater(ctx, logger, txMgr, addr, client)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to create the cannon updater: %w", err) return nil, fmt.Errorf("failed to create the cannon updater: %w", err)
...@@ -94,6 +98,11 @@ func NewGamePlayer( ...@@ -94,6 +98,11 @@ func NewGamePlayer(
} }
func (g *GamePlayer) ProgressGame(ctx context.Context) bool { func (g *GamePlayer) ProgressGame(ctx context.Context) bool {
if g.completed {
// Game is already complete so don't try to perform further actions.
g.logger.Trace("Skipping completed game")
return true
}
g.logger.Trace("Checking if actions are required") g.logger.Trace("Checking if actions are required")
if err := g.agent.Act(ctx); err != nil { if err := g.agent.Act(ctx); err != nil {
g.logger.Error("Error when acting on game", "err", err) g.logger.Error("Error when acting on game", "err", err)
...@@ -102,7 +111,8 @@ func (g *GamePlayer) ProgressGame(ctx context.Context) bool { ...@@ -102,7 +111,8 @@ func (g *GamePlayer) ProgressGame(ctx context.Context) bool {
g.logger.Warn("Unable to retrieve game status", "err", err) g.logger.Warn("Unable to retrieve game status", "err", err)
} else { } else {
g.logGameStatus(ctx, status) g.logGameStatus(ctx, status)
return status != types.GameStatusInProgress g.completed = status != types.GameStatusInProgress
return g.completed
} }
return false return false
} }
......
...@@ -12,14 +12,14 @@ import ( ...@@ -12,14 +12,14 @@ import (
) )
func TestProgressGame_LogErrorFromAct(t *testing.T) { func TestProgressGame_LogErrorFromAct(t *testing.T) {
handler, game, actor, _ := setupProgressGameTest(t, true) handler, game, actor := setupProgressGameTest(t, true)
actor.err = errors.New("boom") actor.actErr = errors.New("boom")
done := game.ProgressGame(context.Background()) done := game.ProgressGame(context.Background())
require.False(t, done, "should not be done") require.False(t, done, "should not be done")
require.Equal(t, 1, actor.callCount, "should perform next actions") require.Equal(t, 1, actor.callCount, "should perform next actions")
errLog := handler.FindLog(log.LvlError, "Error when acting on game") errLog := handler.FindLog(log.LvlError, "Error when acting on game")
require.NotNil(t, errLog, "should log error") require.NotNil(t, errLog, "should log error")
require.Equal(t, actor.err, errLog.GetContextValue("err")) require.Equal(t, actor.actErr, errLog.GetContextValue("err"))
// Should still log game status // Should still log game status
msg := handler.FindLog(log.LvlInfo, "Game info") msg := handler.FindLog(log.LvlInfo, "Game info")
...@@ -74,11 +74,11 @@ func TestProgressGame_LogGameStatus(t *testing.T) { ...@@ -74,11 +74,11 @@ func TestProgressGame_LogGameStatus(t *testing.T) {
for _, test := range tests { for _, test := range tests {
test := test test := test
t.Run(test.name, func(t *testing.T) { t.Run(test.name, func(t *testing.T) {
handler, game, actor, gameInfo := setupProgressGameTest(t, test.agreeWithOutput) handler, game, gameState := setupProgressGameTest(t, test.agreeWithOutput)
gameInfo.status = test.status gameState.status = test.status
done := game.ProgressGame(context.Background()) done := game.ProgressGame(context.Background())
require.Equal(t, 1, actor.callCount, "should perform next actions") require.Equal(t, 1, gameState.callCount, "should perform next actions")
require.Equal(t, test.status != types.GameStatusInProgress, done, "should be done when not in progress") require.Equal(t, test.status != types.GameStatusInProgress, done, "should be done when not in progress")
errLog := handler.FindLog(test.logLevel, test.logMsg) errLog := handler.FindLog(test.logLevel, test.logMsg)
require.NotNil(t, errLog, "should log game result") require.NotNil(t, errLog, "should log game result")
...@@ -87,43 +87,57 @@ func TestProgressGame_LogGameStatus(t *testing.T) { ...@@ -87,43 +87,57 @@ func TestProgressGame_LogGameStatus(t *testing.T) {
} }
} }
func setupProgressGameTest(t *testing.T, agreeWithProposedRoot bool) (*testlog.CapturingHandler, *GamePlayer, *stubActor, *stubGameInfo) { func TestDoNotActOnCompleteGame(t *testing.T) {
for _, status := range []types.GameStatus{types.GameStatusChallengerWon, types.GameStatusDefenderWon} {
t.Run(status.String(), func(t *testing.T) {
_, game, gameState := setupProgressGameTest(t, true)
gameState.status = status
done := game.ProgressGame(context.Background())
require.Equal(t, 1, gameState.callCount, "acts the first time")
require.True(t, done, "should be done")
// Should not act when it knows the game is already complete
done = game.ProgressGame(context.Background())
require.Equal(t, 1, gameState.callCount, "does not act after game is complete")
require.True(t, done, "should still be done")
})
}
}
func setupProgressGameTest(t *testing.T, agreeWithProposedRoot bool) (*testlog.CapturingHandler, *GamePlayer, *stubGameState) {
logger := testlog.Logger(t, log.LvlDebug) logger := testlog.Logger(t, log.LvlDebug)
handler := &testlog.CapturingHandler{ handler := &testlog.CapturingHandler{
Delegate: logger.GetHandler(), Delegate: logger.GetHandler(),
} }
logger.SetHandler(handler) logger.SetHandler(handler)
actor := &stubActor{} gameState := &stubGameState{claimCount: 1}
gameInfo := &stubGameInfo{claimCount: 1}
game := &GamePlayer{ game := &GamePlayer{
agent: actor, agent: gameState,
agreeWithProposedOutput: agreeWithProposedRoot, agreeWithProposedOutput: agreeWithProposedRoot,
caller: gameInfo, caller: gameState,
logger: logger, logger: logger,
} }
return handler, game, actor, gameInfo return handler, game, gameState
} }
type stubActor struct { type stubGameState struct {
callCount int
err error
}
func (a *stubActor) Act(ctx context.Context) error {
a.callCount++
return a.err
}
type stubGameInfo struct {
status types.GameStatus status types.GameStatus
claimCount uint64 claimCount uint64
err error callCount int
actErr error
Err error
}
func (s *stubGameState) Act(ctx context.Context) error {
s.callCount++
return s.actErr
} }
func (s *stubGameInfo) GetGameStatus(ctx context.Context) (types.GameStatus, error) { func (s *stubGameState) GetGameStatus(ctx context.Context) (types.GameStatus, error) {
return s.status, s.err return s.status, nil
} }
func (s *stubGameInfo) GetClaimCount(ctx context.Context) (uint64, error) { func (s *stubGameState) GetClaimCount(ctx context.Context) (uint64, error) {
return s.claimCount, s.err return s.claimCount, nil
} }
...@@ -79,18 +79,25 @@ func (r *faultResponder) BuildTx(ctx context.Context, response types.Claim) ([]b ...@@ -79,18 +79,25 @@ func (r *faultResponder) BuildTx(ctx context.Context, response types.Claim) ([]b
} }
} }
// CanResolve determines if the resolve function on the fault dispute game contract // CallResolve determines if the resolve function on the fault dispute game contract
// would succeed. Returns true if the game can be resolved, otherwise false. // would succeed. Returns the game status if the call would succeed, errors otherwise.
func (r *faultResponder) CanResolve(ctx context.Context) bool { func (r *faultResponder) CallResolve(ctx context.Context) (types.GameStatus, error) {
txData, err := r.buildResolveData() txData, err := r.buildResolveData()
if err != nil { if err != nil {
return false return types.GameStatusInProgress, err
} }
_, err = r.txMgr.Call(ctx, ethereum.CallMsg{ res, err := r.txMgr.Call(ctx, ethereum.CallMsg{
To: &r.fdgAddr, To: &r.fdgAddr,
Data: txData, Data: txData,
}, nil) }, nil)
return err == nil if err != nil {
return types.GameStatusInProgress, err
}
var status uint8
if err = r.fdgAbi.UnpackIntoInterface(&status, "resolve", res); err != nil {
return types.GameStatusInProgress, err
}
return types.GameStatusFromUint8(status)
} }
// Resolve executes a resolve transaction to resolve a fault dispute game. // Resolve executes a resolve transaction to resolve a fault dispute game.
......
This diff is collapsed.
...@@ -73,9 +73,18 @@ func NewService(ctx context.Context, logger log.Logger, cfg *config.Config) (*se ...@@ -73,9 +73,18 @@ func NewService(ctx context.Context, logger log.Logger, cfg *config.Config) (*se
} }
loader := NewGameLoader(factory) loader := NewGameLoader(factory)
monitor := newGameMonitor(logger, cl, client.BlockNumber, cfg.GameAllowlist, loader, func(addr common.Address) (gamePlayer, error) { disk := newDiskManager(cfg.Datadir)
return NewGamePlayer(ctx, logger, cfg, addr, txMgr, client) monitor := newGameMonitor(
}) logger,
cfg.GameWindow,
cl,
disk,
client.BlockNumber,
cfg.GameAllowlist,
loader,
func(addr common.Address, dir string) (gamePlayer, error) {
return NewGamePlayer(ctx, logger, cfg, dir, addr, txMgr, client)
})
m.RecordInfo(version.SimpleWithMeta) m.RecordInfo(version.SimpleWithMeta)
m.RecordUp() m.RecordUp()
......
...@@ -3,6 +3,7 @@ package types ...@@ -3,6 +3,7 @@ package types
import ( import (
"context" "context"
"errors" "errors"
"fmt"
"math/big" "math/big"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
...@@ -34,6 +35,14 @@ func (s GameStatus) String() string { ...@@ -34,6 +35,14 @@ func (s GameStatus) String() string {
} }
} }
// GameStatusFromUint8 returns a game status from the uint8 representation.
func GameStatusFromUint8(i uint8) (GameStatus, error) {
if i > 2 {
return GameStatus(i), fmt.Errorf("invalid game status: %d", i)
}
return GameStatus(i), nil
}
// PreimageOracleData encapsulates the preimage oracle data // PreimageOracleData encapsulates the preimage oracle data
// to load into the onchain oracle. // to load into the onchain oracle.
type PreimageOracleData struct { type PreimageOracleData struct {
......
package types package types
import ( import (
"fmt"
"testing" "testing"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
var validGameStatuses = []GameStatus{
GameStatusInProgress,
GameStatusChallengerWon,
GameStatusDefenderWon,
}
func TestGameStatusFromUint8(t *testing.T) {
for _, status := range validGameStatuses {
t.Run(fmt.Sprintf("Valid Game Status %v", status), func(t *testing.T) {
parsed, err := GameStatusFromUint8(uint8(status))
require.NoError(t, err)
require.Equal(t, status, parsed)
})
}
t.Run("Invalid", func(t *testing.T) {
status, err := GameStatusFromUint8(3)
require.Error(t, err)
require.Equal(t, GameStatus(3), status)
})
}
func TestNewPreimageOracleData(t *testing.T) { func TestNewPreimageOracleData(t *testing.T) {
t.Run("LocalData", func(t *testing.T) { t.Run("LocalData", func(t *testing.T) {
data := NewPreimageOracleData([]byte{1, 2, 3}, []byte{4, 5, 6}, 7) data := NewPreimageOracleData([]byte{1, 2, 3}, []byte{4, 5, 6}, 7)
......
...@@ -57,6 +57,11 @@ var ( ...@@ -57,6 +57,11 @@ var (
Usage: "Temporary hardcoded flag if we agree or disagree with the proposed output.", Usage: "Temporary hardcoded flag if we agree or disagree with the proposed output.",
EnvVars: prefixEnvVars("AGREE_WITH_PROPOSED_OUTPUT"), EnvVars: prefixEnvVars("AGREE_WITH_PROPOSED_OUTPUT"),
} }
DatadirFlag = &cli.StringFlag{
Name: "datadir",
Usage: "Directory to store data generated as part of responding to games",
EnvVars: prefixEnvVars("DATADIR"),
}
// Optional Flags // Optional Flags
AlphabetFlag = &cli.StringFlag{ AlphabetFlag = &cli.StringFlag{
Name: "alphabet", Name: "alphabet",
...@@ -93,11 +98,6 @@ var ( ...@@ -93,11 +98,6 @@ var (
Usage: "Path to absolute prestate to use when generating trace data (cannon trace type only)", Usage: "Path to absolute prestate to use when generating trace data (cannon trace type only)",
EnvVars: prefixEnvVars("CANNON_PRESTATE"), EnvVars: prefixEnvVars("CANNON_PRESTATE"),
} }
CannonDatadirFlag = &cli.StringFlag{
Name: "cannon-datadir",
Usage: "Directory to store data generated by cannon (cannon trace type only)",
EnvVars: prefixEnvVars("CANNON_DATADIR"),
}
CannonL2Flag = &cli.StringFlag{ CannonL2Flag = &cli.StringFlag{
Name: "cannon-l2", Name: "cannon-l2",
Usage: "L2 Address of L2 JSON-RPC endpoint to use (eth and debug namespace required) (cannon trace type only)", Usage: "L2 Address of L2 JSON-RPC endpoint to use (eth and debug namespace required) (cannon trace type only)",
...@@ -109,6 +109,12 @@ var ( ...@@ -109,6 +109,12 @@ var (
EnvVars: prefixEnvVars("CANNON_SNAPSHOT_FREQ"), EnvVars: prefixEnvVars("CANNON_SNAPSHOT_FREQ"),
Value: config.DefaultCannonSnapshotFreq, Value: config.DefaultCannonSnapshotFreq,
} }
GameWindowFlag = &cli.DurationFlag{
Name: "game-window",
Usage: "The time window which the challenger will look for games to progress.",
EnvVars: prefixEnvVars("GAME_WINDOW"),
Value: config.DefaultGameWindow,
}
) )
// requiredFlags are checked by [CheckRequired] // requiredFlags are checked by [CheckRequired]
...@@ -117,6 +123,7 @@ var requiredFlags = []cli.Flag{ ...@@ -117,6 +123,7 @@ var requiredFlags = []cli.Flag{
FactoryAddressFlag, FactoryAddressFlag,
TraceTypeFlag, TraceTypeFlag,
AgreeWithProposedOutputFlag, AgreeWithProposedOutputFlag,
DatadirFlag,
} }
// optionalFlags is a list of unchecked cli flags // optionalFlags is a list of unchecked cli flags
...@@ -129,9 +136,9 @@ var optionalFlags = []cli.Flag{ ...@@ -129,9 +136,9 @@ var optionalFlags = []cli.Flag{
CannonBinFlag, CannonBinFlag,
CannonServerFlag, CannonServerFlag,
CannonPreStateFlag, CannonPreStateFlag,
CannonDatadirFlag,
CannonL2Flag, CannonL2Flag,
CannonSnapshotFreqFlag, CannonSnapshotFreqFlag,
GameWindowFlag,
} }
func init() { func init() {
...@@ -174,9 +181,6 @@ func CheckRequired(ctx *cli.Context) error { ...@@ -174,9 +181,6 @@ func CheckRequired(ctx *cli.Context) error {
if !ctx.IsSet(CannonPreStateFlag.Name) { if !ctx.IsSet(CannonPreStateFlag.Name) {
return fmt.Errorf("flag %s is required", CannonPreStateFlag.Name) return fmt.Errorf("flag %s is required", CannonPreStateFlag.Name)
} }
if !ctx.IsSet(CannonDatadirFlag.Name) {
return fmt.Errorf("flag %s is required", CannonDatadirFlag.Name)
}
if !ctx.IsSet(CannonL2Flag.Name) { if !ctx.IsSet(CannonL2Flag.Name) {
return fmt.Errorf("flag %s is required", CannonL2Flag.Name) return fmt.Errorf("flag %s is required", CannonL2Flag.Name)
} }
...@@ -222,6 +226,7 @@ func NewConfigFromCLI(ctx *cli.Context) (*config.Config, error) { ...@@ -222,6 +226,7 @@ func NewConfigFromCLI(ctx *cli.Context) (*config.Config, error) {
TraceType: traceTypeFlag, TraceType: traceTypeFlag,
GameFactoryAddress: gameFactoryAddress, GameFactoryAddress: gameFactoryAddress,
GameAllowlist: allowedGames, GameAllowlist: allowedGames,
GameWindow: ctx.Duration(GameWindowFlag.Name),
AlphabetTrace: ctx.String(AlphabetFlag.Name), AlphabetTrace: ctx.String(AlphabetFlag.Name),
CannonNetwork: ctx.String(CannonNetworkFlag.Name), CannonNetwork: ctx.String(CannonNetworkFlag.Name),
CannonRollupConfigPath: ctx.String(CannonRollupConfigFlag.Name), CannonRollupConfigPath: ctx.String(CannonRollupConfigFlag.Name),
...@@ -229,7 +234,7 @@ func NewConfigFromCLI(ctx *cli.Context) (*config.Config, error) { ...@@ -229,7 +234,7 @@ func NewConfigFromCLI(ctx *cli.Context) (*config.Config, error) {
CannonBin: ctx.String(CannonBinFlag.Name), CannonBin: ctx.String(CannonBinFlag.Name),
CannonServer: ctx.String(CannonServerFlag.Name), CannonServer: ctx.String(CannonServerFlag.Name),
CannonAbsolutePreState: ctx.String(CannonPreStateFlag.Name), CannonAbsolutePreState: ctx.String(CannonPreStateFlag.Name),
CannonDatadir: ctx.String(CannonDatadirFlag.Name), Datadir: ctx.String(DatadirFlag.Name),
CannonL2: ctx.String(CannonL2Flag.Name), CannonL2: ctx.String(CannonL2Flag.Name),
CannonSnapshotFreq: ctx.Uint(CannonSnapshotFreqFlag.Name), CannonSnapshotFreq: ctx.Uint(CannonSnapshotFreqFlag.Name),
AgreeWithProposedOutput: ctx.Bool(AgreeWithProposedOutputFlag.Name), AgreeWithProposedOutput: ctx.Bool(AgreeWithProposedOutputFlag.Name),
......
...@@ -19,12 +19,19 @@ DISPUTE_GAME_PROXY=$(jq -r .DisputeGameFactoryProxy $MONOREPO_DIR/.devnet/addres ...@@ -19,12 +19,19 @@ DISPUTE_GAME_PROXY=$(jq -r .DisputeGameFactoryProxy $MONOREPO_DIR/.devnet/addres
FAULT_GAME_ADDRESS=$(cat $FAULT_GAME_ADDR_FILE) FAULT_GAME_ADDRESS=$(cat $FAULT_GAME_ADDR_FILE)
echo "Fault dispute game address: $FAULT_GAME_ADDRESS" echo "Fault dispute game address: $FAULT_GAME_ADDRESS"
DATADIR=`mktemp -d`
trap cleanup SIGINT
cleanup(){
rm -rf "${DATADIR}"
}
$CHALLENGER_DIR/bin/op-challenger \ $CHALLENGER_DIR/bin/op-challenger \
--l1-eth-rpc http://localhost:8545 \ --l1-eth-rpc http://localhost:8545 \
--trace-type="alphabet" \ --trace-type="alphabet" \
--alphabet "abcdefgh" \ --alphabet "abcdefgh" \
--datadir "${DATADIR}" \
--game-factory-address $DISPUTE_GAME_PROXY \ --game-factory-address $DISPUTE_GAME_PROXY \
--game-address $FAULT_GAME_ADDRESS \ --game-allowlist $FAULT_GAME_ADDRESS \
--private-key $CHARLIE_KEY \ --private-key $CHARLIE_KEY \
--num-confirmations 1 \ --num-confirmations 1 \
--metrics.enabled --metrics.port=7304 \ --metrics.enabled --metrics.port=7304 \
......
...@@ -19,12 +19,19 @@ DISPUTE_GAME_PROXY=$(jq -r .DisputeGameFactoryProxy $MONOREPO_DIR/.devnet/addres ...@@ -19,12 +19,19 @@ DISPUTE_GAME_PROXY=$(jq -r .DisputeGameFactoryProxy $MONOREPO_DIR/.devnet/addres
FAULT_GAME_ADDRESS=$(cat $FAULT_GAME_ADDR_FILE) FAULT_GAME_ADDRESS=$(cat $FAULT_GAME_ADDR_FILE)
echo "Fault dispute game address: $FAULT_GAME_ADDRESS" echo "Fault dispute game address: $FAULT_GAME_ADDRESS"
DATADIR=`mktemp -d`
trap cleanup SIGINT
cleanup(){
rm -rf "${DATADIR}"
}
$CHALLENGER_DIR/bin/op-challenger \ $CHALLENGER_DIR/bin/op-challenger \
--l1-eth-rpc http://localhost:8545 \ --l1-eth-rpc http://localhost:8545 \
--trace-type="alphabet" \ --trace-type="alphabet" \
--alphabet "abcdexyz" \ --alphabet "abcdexyz" \
--datadir "${DATADIR}" \
--game-factory-address $DISPUTE_GAME_PROXY \ --game-factory-address $DISPUTE_GAME_PROXY \
--game-address $FAULT_GAME_ADDRESS \ --game-allowlist $FAULT_GAME_ADDRESS \
--private-key $MALLORY_KEY \ --private-key $MALLORY_KEY \
--num-confirmations 1 \ --num-confirmations 1 \
--metrics.enabled --metrics.port=7305 \ --metrics.enabled --metrics.port=7305 \
......
...@@ -23,9 +23,11 @@ import ( ...@@ -23,9 +23,11 @@ import (
) )
type Helper struct { type Helper struct {
log log.Logger log log.Logger
cancel func() require *require.Assertions
errors chan error dir string
cancel func()
errors chan error
} }
type Option func(config2 *config.Config) type Option func(config2 *config.Config)
...@@ -74,7 +76,6 @@ func WithCannon( ...@@ -74,7 +76,6 @@ func WithCannon(
require := require.New(t) require := require.New(t)
c.TraceType = config.TraceTypeCannon c.TraceType = config.TraceTypeCannon
c.CannonL2 = l2Endpoint c.CannonL2 = l2Endpoint
c.CannonDatadir = t.TempDir()
c.CannonBin = "../cannon/bin/cannon" c.CannonBin = "../cannon/bin/cannon"
c.CannonServer = "../op-program/bin/op-program" c.CannonServer = "../op-program/bin/op-program"
c.CannonAbsolutePreState = "../op-program/bin/prestate.json" c.CannonAbsolutePreState = "../op-program/bin/prestate.json"
...@@ -82,13 +83,13 @@ func WithCannon( ...@@ -82,13 +83,13 @@ func WithCannon(
genesisBytes, err := json.Marshal(l2Genesis) genesisBytes, err := json.Marshal(l2Genesis)
require.NoError(err, "marshall l2 genesis config") require.NoError(err, "marshall l2 genesis config")
genesisFile := filepath.Join(c.CannonDatadir, "l2-genesis.json") genesisFile := filepath.Join(c.Datadir, "l2-genesis.json")
require.NoError(os.WriteFile(genesisFile, genesisBytes, 0644)) require.NoError(os.WriteFile(genesisFile, genesisBytes, 0644))
c.CannonL2GenesisPath = genesisFile c.CannonL2GenesisPath = genesisFile
rollupBytes, err := json.Marshal(rollupCfg) rollupBytes, err := json.Marshal(rollupCfg)
require.NoError(err, "marshall rollup config") require.NoError(err, "marshall rollup config")
rollupFile := filepath.Join(c.CannonDatadir, "rollup.json") rollupFile := filepath.Join(c.Datadir, "rollup.json")
require.NoError(os.WriteFile(rollupFile, rollupBytes, 0644)) require.NoError(os.WriteFile(rollupFile, rollupBytes, 0644))
c.CannonRollupConfigPath = rollupFile c.CannonRollupConfigPath = rollupFile
} }
...@@ -106,9 +107,11 @@ func NewChallenger(t *testing.T, ctx context.Context, l1Endpoint string, name st ...@@ -106,9 +107,11 @@ func NewChallenger(t *testing.T, ctx context.Context, l1Endpoint string, name st
errCh <- op_challenger.Main(ctx, log, cfg) errCh <- op_challenger.Main(ctx, log, cfg)
}() }()
return &Helper{ return &Helper{
log: log, log: log,
cancel: cancel, require: require.New(t),
errors: errCh, dir: cfg.Datadir,
cancel: cancel,
errors: errCh,
} }
} }
...@@ -121,6 +124,7 @@ func NewChallengerConfig(t *testing.T, l1Endpoint string, options ...Option) *co ...@@ -121,6 +124,7 @@ func NewChallengerConfig(t *testing.T, l1Endpoint string, options ...Option) *co
AlphabetTrace: "", AlphabetTrace: "",
AgreeWithProposedOutput: true, AgreeWithProposedOutput: true,
TxMgrConfig: txmgrCfg, TxMgrConfig: txmgrCfg,
Datadir: t.TempDir(),
} }
for _, option := range options { for _, option := range options {
option(cfg) option(cfg)
...@@ -155,3 +159,25 @@ func (h *Helper) Close() error { ...@@ -155,3 +159,25 @@ func (h *Helper) Close() error {
return nil return nil
} }
} }
type GameAddr interface {
Addr() common.Address
}
func (h *Helper) VerifyGameDataExists(games ...GameAddr) {
for _, game := range games {
addr := game.Addr()
h.require.DirExistsf(h.gameDataDir(addr), "should have data for game %v", addr)
}
}
func (h *Helper) VerifyNoGameDataExists(games ...GameAddr) {
for _, game := range games {
addr := game.Addr()
h.require.NoDirExistsf(h.gameDataDir(addr), "should have data for game %v", addr)
}
}
func (h *Helper) gameDataDir(addr common.Address) string {
return filepath.Join(h.dir, addr.Hex())
}
...@@ -2,6 +2,7 @@ package disputegame ...@@ -2,6 +2,7 @@ package disputegame
import ( import (
"context" "context"
"path/filepath"
"github.com/ethereum-optimism/optimism/op-challenger/fault/cannon" "github.com/ethereum-optimism/optimism/op-challenger/fault/cannon"
"github.com/ethereum-optimism/optimism/op-e2e/e2eutils/challenger" "github.com/ethereum-optimism/optimism/op-e2e/e2eutils/challenger"
...@@ -38,7 +39,8 @@ func (g *CannonGameHelper) CreateHonestActor(ctx context.Context, rollupCfg *rol ...@@ -38,7 +39,8 @@ func (g *CannonGameHelper) CreateHonestActor(ctx context.Context, rollupCfg *rol
} }
opts = append(opts, options...) opts = append(opts, options...)
cfg := challenger.NewChallengerConfig(g.t, l1Endpoint, opts...) cfg := challenger.NewChallengerConfig(g.t, l1Endpoint, opts...)
provider, err := cannon.NewTraceProvider(ctx, testlog.Logger(g.t, log.LvlInfo).New("role", "CorrectTrace"), cfg, l1Client, g.addr) logger := testlog.Logger(g.t, log.LvlInfo).New("role", "CorrectTrace")
provider, err := cannon.NewTraceProvider(ctx, logger, cfg, l1Client, filepath.Join(cfg.Datadir, "honest"), g.addr)
g.require.NoError(err, "create cannon trace provider") g.require.NoError(err, "create cannon trace provider")
return &HonestHelper{ return &HonestHelper{
......
...@@ -26,6 +26,10 @@ type FaultGameHelper struct { ...@@ -26,6 +26,10 @@ type FaultGameHelper struct {
addr common.Address addr common.Address
} }
func (g *FaultGameHelper) Addr() common.Address {
return g.addr
}
func (g *FaultGameHelper) GameDuration(ctx context.Context) time.Duration { func (g *FaultGameHelper) GameDuration(ctx context.Context) time.Duration {
duration, err := g.game.GAMEDURATION(&bind.CallOpts{Context: ctx}) duration, err := g.game.GAMEDURATION(&bind.CallOpts{Context: ctx})
g.require.NoError(err, "failed to get game duration") g.require.NoError(err, "failed to get game duration")
......
...@@ -175,7 +175,7 @@ func (h *FactoryHelper) StartCannonGameWithCorrectRoot(ctx context.Context, roll ...@@ -175,7 +175,7 @@ func (h *FactoryHelper) StartCannonGameWithCorrectRoot(ctx context.Context, roll
L2Claim: challengedOutput.OutputRoot, L2Claim: challengedOutput.OutputRoot,
L2BlockNumber: challengedOutput.L2BlockNumber, L2BlockNumber: challengedOutput.L2BlockNumber,
} }
provider := cannon.NewTraceProviderFromInputs(testlog.Logger(h.t, log.LvlInfo).New("role", "CorrectTrace"), cfg, "correct", inputs) provider := cannon.NewTraceProviderFromInputs(testlog.Logger(h.t, log.LvlInfo).New("role", "CorrectTrace"), cfg, inputs, cfg.Datadir)
rootClaim, err := provider.Get(ctx, math.MaxUint64) rootClaim, err := provider.Get(ctx, math.MaxUint64)
h.require.NoError(err, "Compute correct root hash") h.require.NoError(err, "Compute correct root hash")
......
...@@ -58,7 +58,7 @@ func TestMultipleCannonGames(t *testing.T) { ...@@ -58,7 +58,7 @@ func TestMultipleCannonGames(t *testing.T) {
gameFactory := disputegame.NewFactoryHelper(t, ctx, sys.cfg.L1Deployments, l1Client) gameFactory := disputegame.NewFactoryHelper(t, ctx, sys.cfg.L1Deployments, l1Client)
// Start a challenger with the correct alphabet trace // Start a challenger with the correct alphabet trace
gameFactory.StartChallenger(ctx, sys.NodeEndpoint("l1"), "TowerDefense", challenger := gameFactory.StartChallenger(ctx, sys.NodeEndpoint("l1"), "TowerDefense",
challenger.WithCannon(t, sys.RollupConfig, sys.L2GenesisCfg, sys.NodeEndpoint("sequencer")), challenger.WithCannon(t, sys.RollupConfig, sys.L2GenesisCfg, sys.NodeEndpoint("sequencer")),
challenger.WithPrivKey(sys.cfg.Secrets.Alice), challenger.WithPrivKey(sys.cfg.Secrets.Alice),
challenger.WithAgreeProposedOutput(true), challenger.WithAgreeProposedOutput(true),
...@@ -74,6 +74,9 @@ func TestMultipleCannonGames(t *testing.T) { ...@@ -74,6 +74,9 @@ func TestMultipleCannonGames(t *testing.T) {
game2Claim := game2.GetClaimValue(ctx, 1) game2Claim := game2.GetClaimValue(ctx, 1)
require.NotEqual(t, game1Claim, game2Claim, "games should have different cannon traces") require.NotEqual(t, game1Claim, game2Claim, "games should have different cannon traces")
// Check that the helper finds the game directories correctly
challenger.VerifyGameDataExists(game1, game2)
// Push both games down to the step function // Push both games down to the step function
maxDepth := game1.MaxDepth(ctx) maxDepth := game1.MaxDepth(ctx)
for claimCount := int64(1); claimCount <= maxDepth; { for claimCount := int64(1); claimCount <= maxDepth; {
...@@ -97,8 +100,9 @@ func TestMultipleCannonGames(t *testing.T) { ...@@ -97,8 +100,9 @@ func TestMultipleCannonGames(t *testing.T) {
game1.WaitForGameStatus(ctx, disputegame.StatusChallengerWins) game1.WaitForGameStatus(ctx, disputegame.StatusChallengerWins)
game2.WaitForGameStatus(ctx, disputegame.StatusChallengerWins) game2.WaitForGameStatus(ctx, disputegame.StatusChallengerWins)
game1.LogGameData(ctx)
game2.LogGameData(ctx) // Check that the game directories are removed
challenger.VerifyNoGameDataExists(game1, game2)
} }
func TestResolveDisputeGame(t *testing.T) { func TestResolveDisputeGame(t *testing.T) {
......
FROM golang:1.19.9-alpine3.16 as builder FROM golang:1.20.7-alpine3.18 as builder
# build from root of repo # build from root of repo
COPY ./op-exporter /app COPY ./op-exporter /app
...@@ -7,7 +7,7 @@ WORKDIR /app/ ...@@ -7,7 +7,7 @@ WORKDIR /app/
RUN apk --no-cache add make bash jq git RUN apk --no-cache add make bash jq git
RUN make build RUN make build
FROM alpine:3.16 FROM alpine:3.18
RUN apk --no-cache add ca-certificates RUN apk --no-cache add ca-certificates
WORKDIR /root/ WORKDIR /root/
COPY --from=builder /app/op-exporter /usr/local/bin/ COPY --from=builder /app/op-exporter /usr/local/bin/
......
module github.com/ethereum-optimism/optimism/op-exporter module github.com/ethereum-optimism/optimism/op-exporter
go 1.18 go 1.20
require ( require (
github.com/ethereum/go-ethereum v1.10.17 github.com/ethereum/go-ethereum v1.10.17
......
FROM golang:1.19.9-alpine3.16 as builder FROM golang:1.20.7-alpine3.18 as builder
RUN apk add --no-cache make gcc musl-dev linux-headers git jq bash RUN apk add --no-cache make gcc musl-dev linux-headers git jq bash
...@@ -15,7 +15,7 @@ WORKDIR /app/op-heartbeat ...@@ -15,7 +15,7 @@ WORKDIR /app/op-heartbeat
RUN make op-heartbeat RUN make op-heartbeat
FROM alpine:3.16 FROM alpine:3.18
COPY --from=builder /app/op-heartbeat/bin/op-heartbeat /usr/local/bin COPY --from=builder /app/op-heartbeat/bin/op-heartbeat /usr/local/bin
......
FROM --platform=$BUILDPLATFORM golang:1.19.9-alpine3.16 as builder FROM --platform=$BUILDPLATFORM golang:1.20.7-alpine3.18 as builder
ARG VERSION=v0.0.0 ARG VERSION=v0.0.0
...@@ -21,7 +21,7 @@ ARG TARGETOS TARGETARCH ...@@ -21,7 +21,7 @@ ARG TARGETOS TARGETARCH
RUN make op-node VERSION="$VERSION" GOOS=$TARGETOS GOARCH=$TARGETARCH RUN make op-node VERSION="$VERSION" GOOS=$TARGETOS GOARCH=$TARGETARCH
FROM alpine:3.16 FROM alpine:3.18
COPY --from=builder /app/op-node/bin/op-node /usr/local/bin COPY --from=builder /app/op-node/bin/op-node /usr/local/bin
......
package derive package derive
import ( import (
crand "crypto/rand"
"math/big" "math/big"
"math/rand" "math/rand"
"testing" "testing"
...@@ -97,7 +98,7 @@ func TestParseL1InfoDepositTxData(t *testing.T) { ...@@ -97,7 +98,7 @@ func TestParseL1InfoDepositTxData(t *testing.T) {
info := testutils.MakeBlockInfo(nil)(rng) info := testutils.MakeBlockInfo(nil)(rng)
depTx, err := L1InfoDeposit(randomSeqNr(rng), info, randomL1Cfg(rng, info), false) depTx, err := L1InfoDeposit(randomSeqNr(rng), info, randomL1Cfg(rng, info), false)
require.NoError(t, err) require.NoError(t, err)
_, err = rand.Read(depTx.Data[0:4]) _, err = crand.Read(depTx.Data[0:4])
require.NoError(t, err) require.NoError(t, err)
_, err = L1InfoDepositTxData(depTx.Data) _, err = L1InfoDepositTxData(depTx.Data)
require.ErrorContains(t, err, "function signature") require.ErrorContains(t, err, "function signature")
......
...@@ -2,8 +2,8 @@ package sources ...@@ -2,8 +2,8 @@ package sources
import ( import (
"context" "context"
crand "crypto/rand"
"math/big" "math/big"
"math/rand"
"testing" "testing"
"github.com/stretchr/testify/mock" "github.com/stretchr/testify/mock"
...@@ -56,7 +56,7 @@ var testEthClientConfig = &EthClientConfig{ ...@@ -56,7 +56,7 @@ var testEthClientConfig = &EthClientConfig{
} }
func randHash() (out common.Hash) { func randHash() (out common.Hash) {
rand.Read(out[:]) _, _ = crand.Read(out[:])
return out return out
} }
......
FROM --platform=$BUILDPLATFORM golang:1.19.9-alpine3.16 as builder FROM --platform=$BUILDPLATFORM golang:1.20.7-alpine3.18 as builder
ARG VERSION=v0.0.0 ARG VERSION=v0.0.0
...@@ -23,7 +23,7 @@ ARG TARGETOS TARGETARCH ...@@ -23,7 +23,7 @@ ARG TARGETOS TARGETARCH
RUN make op-program VERSION="$VERSION" GOOS=$TARGETOS GOARCH=$TARGETARCH RUN make op-program VERSION="$VERSION" GOOS=$TARGETOS GOARCH=$TARGETARCH
FROM alpine:3.16 FROM alpine:3.18
COPY --from=builder /app/op-program/bin/op-program /usr/local/bin COPY --from=builder /app/op-program/bin/op-program /usr/local/bin
......
FROM --platform=$BUILDPLATFORM golang:1.19.9-alpine3.16 as builder FROM --platform=$BUILDPLATFORM golang:1.20.7-alpine3.18 as builder
ARG VERSION=v0.0.0 ARG VERSION=v0.0.0
...@@ -22,7 +22,7 @@ ARG TARGETOS TARGETARCH ...@@ -22,7 +22,7 @@ ARG TARGETOS TARGETARCH
RUN make op-proposer VERSION="$VERSION" GOOS=$TARGETOS GOARCH=$TARGETARCH RUN make op-proposer VERSION="$VERSION" GOOS=$TARGETOS GOARCH=$TARGETARCH
FROM alpine:3.16 FROM alpine:3.18
COPY --from=builder /app/op-proposer/bin/op-proposer /usr/local/bin COPY --from=builder /app/op-proposer/bin/op-proposer /usr/local/bin
......
FROM golang:1.19.9-alpine3.16 as builder FROM golang:1.20.7-alpine3.18 as builder
RUN apk add --no-cache make gcc musl-dev linux-headers RUN apk add --no-cache make gcc musl-dev linux-headers
...@@ -14,7 +14,7 @@ WORKDIR /app/op-wheel ...@@ -14,7 +14,7 @@ WORKDIR /app/op-wheel
RUN go build -o op-wheel ./cmd/main.go RUN go build -o op-wheel ./cmd/main.go
FROM alpine:3.16 FROM alpine:3.18
COPY --from=builder /app/op-wheel/op-wheel /usr/local/bin COPY --from=builder /app/op-wheel/op-wheel /usr/local/bin
......
...@@ -41,7 +41,7 @@ ...@@ -41,7 +41,7 @@
"lint": "pnpm lint:fix && pnpm lint:check" "lint": "pnpm lint:fix && pnpm lint:check"
}, },
"devDependencies": { "devDependencies": {
"@typescript-eslint/eslint-plugin": "^6.4.0", "@typescript-eslint/eslint-plugin": "^5.62.0",
"@typescript-eslint/parser": "^6.4.0", "@typescript-eslint/parser": "^6.4.0",
"tsx": "^3.12.7", "tsx": "^3.12.7",
"typescript": "^5.1.6" "typescript": "^5.1.6"
......
module github.com/ethereum-optimism/optimism/packages/contracts-bedrock/ctb-test-case-generator module github.com/ethereum-optimism/optimism/packages/contracts-bedrock/ctb-test-case-generator
go 1.19 go 1.20
require github.com/ethereum/go-ethereum v1.10.26 require github.com/ethereum/go-ethereum v1.10.26
......
...@@ -48,7 +48,7 @@ ...@@ -48,7 +48,7 @@
}, },
"devDependencies": { "devDependencies": {
"@eth-optimism/contracts-bedrock": "workspace:*", "@eth-optimism/contracts-bedrock": "workspace:*",
"@testing-library/jest-dom": "^5.17.0", "@testing-library/jest-dom": "^6.0.1",
"@testing-library/react-hooks": "^8.0.1", "@testing-library/react-hooks": "^8.0.1",
"@types/glob": "^8.1.0", "@types/glob": "^8.1.0",
"@vitest/coverage-istanbul": "^0.34.1", "@vitest/coverage-istanbul": "^0.34.1",
......
...@@ -35,7 +35,7 @@ ...@@ -35,7 +35,7 @@
}, },
"devDependencies": { "devDependencies": {
"@eth-optimism/contracts-ts": "workspace:^", "@eth-optimism/contracts-ts": "workspace:^",
"@testing-library/jest-dom": "^5.17.0", "@testing-library/jest-dom": "^6.0.1",
"@testing-library/react-hooks": "^8.0.1", "@testing-library/react-hooks": "^8.0.1",
"@vitest/coverage-istanbul": "^0.34.1", "@vitest/coverage-istanbul": "^0.34.1",
"abitype": "^0.9.3", "abitype": "^0.9.3",
......
This diff is collapsed.
module github.com/ethereum-optimism/optimism/proxyd module github.com/ethereum-optimism/optimism/proxyd
go 1.18 go 1.20
require ( require (
github.com/BurntSushi/toml v1.2.0 github.com/BurntSushi/toml v1.2.0
......
...@@ -9,9 +9,9 @@ ...@@ -9,9 +9,9 @@
<!-- END doctoc generated TOC please keep comment here to allow auto update --> <!-- END doctoc generated TOC please keep comment here to allow auto update -->
You can spin up a local devnet via `docker-compose`. You can spin up a local devnet via `docker compose`.
For convenience, we have defined `make` targets to start and stop the devnet with a single command. For convenience, we have defined `make` targets to start and stop the devnet with a single command.
To run the devnet, you will need `docker` and `docker-compose` installed. To run the devnet, you will need `docker` installed.
Then, as a precondition, make sure that you have compiled the contracts by `cd`ing into `packages/contracts` Then, as a precondition, make sure that you have compiled the contracts by `cd`ing into `packages/contracts`
and running `pnpm i` followed by `pnpm build`. You'll only need to do this if you change the contracts in the future. and running `pnpm i` followed by `pnpm build`. You'll only need to do this if you change the contracts in the future.
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment